Skip to content

Commit de3ca05

Browse files
alanshawCopilot
andauthored
feat: revocations clear delegation and key caches (#70)
* feat: revocations clear delegation and key caches * chore: update deps * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * chore: update smelt dependency * fix: rename migration * chore: update hilt * chore: update deps * chore: mod tidy --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 8e278ef commit de3ca05

22 files changed

Lines changed: 1235 additions & 331 deletions

CLAUDE.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ GOWORK=off go test -tags itest ./itest -run 'TestForgeVersity/PutObject' -v # o
3030
GOWORK=off go build -o /tmp/ingot ./cmd/ingot # the daemon binary
3131
```
3232

33-
**go directive: 1.25.7** (the indexing-service dep requires ≥ 1.25.7).
33+
**go directive: 1.26.4** (matches the swarf dep; indexing-service needs ≥ 1.25.7).
3434

3535
**The test pattern — unit first, integration when you're ready to wait.**
3636
`make test` runs library/unit tests in seconds with no Docker. `make itest`
@@ -52,6 +52,8 @@ ingot depends only on these — it must **never** import `fil-forge/sprue` or
5252
`commands/{blob,content,http,assert,ucan,index,provider,access}`, `blobindex`
5353
(sharded-dag-index), `ucan` (ProofStore), `ucan/retrieval`, `didmailto`, `receipt`.
5454
- **`indexing-service/pkg/{client,types}`** — indexer query client.
55+
- **`swarf/pkg/{client,api}`** — the UCAN revocation service client: the SSE
56+
revocation firehose the `revocation/` consumer subscribes to.
5557
- **`fil-forge/versitygw`** — our fork of versity/versitygw, the S3 REST front
5658
end (we implement `backend.Backend`). The fork adds externally derived SigV4
5759
signing keys (`auth.Account.SigningKey`, `middlewares.RequestIAMService`) for
@@ -109,6 +111,10 @@ Internal:
109111
**Forge mode requires Hilt** (`auth_service_url`/`auth_service_did`): the
110112
postgres registry forwards bucket create/delete/list to it, recovering
111113
the signed S3 request from the method's ctx.
114+
- **`revocation/`** — the Swarf firehose consumer (optional,
115+
`revocation_service_url`/`_did`): streams UCAN revocations and clears the
116+
affected access key's iam caches via `iam.Revoker`; resumes from the
117+
`registry.RevocationCursorStore` cursor (no cursor → subscribe from now).
112118
- **`tokenstore/`** — carried-from-guppy delegation store (`tokens.cbor`).
113119
- **`bucket/`** — per-object model: `manifest.go` (`ObjectManifest`, `Body`),
114120
`chunker.go` (`BodyCodec`/`FixedChunker`), `cbor_gen.go`.

config/config.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,14 @@ type Config struct {
7575
// Optional; when empty the client sends invocations with no proofs (Hilt
7676
// may authorize registered provider DIDs directly).
7777
AuthServiceProofs string `mapstructure:"auth_service_proofs" yaml:"auth_service_proofs"`
78+
// RevocationServiceURL / RevocationServiceDID address the UCAN revocation
79+
// service (Swarf): ingot subscribes to its revocation firehose so that
80+
// Hilt's access-key deletions (published as UCAN revocations) clear the
81+
// per-key authorization caches. Optional; when unset the firehose consumer
82+
// is not started and cache entries age out on their own TTLs. Set both or
83+
// neither.
84+
RevocationServiceURL string `mapstructure:"revocation_service_url" yaml:"revocation_service_url"`
85+
RevocationServiceDID string `mapstructure:"revocation_service_did" yaml:"revocation_service_did"`
7886

7987
// MultipartSessionTTL bounds abandoned multipart uploads (Go duration
8088
// string, e.g. "168h"): open sessions older than this are aborted by a
@@ -296,6 +304,9 @@ func (c *Config) Validate() error {
296304
errs = multierr.Append(errs, fmt.Errorf("auth_service_proofs: %w", err))
297305
}
298306
}
307+
if (c.RevocationServiceURL == "") != (c.RevocationServiceDID == "") {
308+
errs = multierr.Append(errs, errors.New("revocation_service_url and revocation_service_did must be set together"))
309+
}
299310

300311
if errs != nil {
301312
return fmt.Errorf("invalid config: %w", errs)

config/config_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ func TestValidate_RequiredFields(t *testing.T) {
5252
{"identity key missing", func(c *Config) { c.Identity.KeyFile = "/nonexistent/agent.pem" }, "identity.key_file"},
5353
{"upload service", func(c *Config) { c.UploadServiceURL = "" }, "upload_service_url and upload_service_did are required"},
5454
{"auth service", func(c *Config) { c.AuthServiceDID = "" }, "auth_service_url and auth_service_did are required"},
55+
{"revocation url without did", func(c *Config) { c.RevocationServiceURL = "http://127.0.0.1:6000" }, "revocation_service_url and revocation_service_did must be set together"},
56+
{"revocation did without url", func(c *Config) { c.RevocationServiceDID = "did:web:swarf.example" }, "revocation_service_url and revocation_service_did must be set together"},
5557
{"bad seal_age", func(c *Config) { c.SealAge = "not-a-duration" }, "parse seal_age"},
5658
{"bad cors origin", func(c *Config) { c.CORSAllowedOrigins = []string{"app.example"} }, "cors_allowed_origins"},
5759
}
@@ -67,6 +69,17 @@ func TestValidate_RequiredFields(t *testing.T) {
6769
}
6870
}
6971

72+
// TestValidate_RevocationServicePair: the revocation service is optional, but
73+
// URL and DID come as a pair.
74+
func TestValidate_RevocationServicePair(t *testing.T) {
75+
cfg := validConfig(t)
76+
cfg.RevocationServiceURL = "http://127.0.0.1:6000"
77+
cfg.RevocationServiceDID = "did:web:swarf.example"
78+
if err := cfg.Validate(); err != nil {
79+
t.Fatalf("expected valid config with revocation pair set, got: %v", err)
80+
}
81+
}
82+
7083
// TestValidate_AuthServiceProofs: the optional proofs value is loaded eagerly
7184
// so a bad path or encoding fails at startup.
7285
func TestValidate_AuthServiceProofs(t *testing.T) {

0 commit comments

Comments
 (0)