feat(cas): emit audit events on artifact uploads and downloads#3189
Conversation
AI Session Analysis
|
| Status | Policy | Material | Messages |
|---|---|---|---|
| ✅ Passed | ai-config-ai-agents-allowed |
ai-coding-session-0c60a3 |
- |
| ✅ Passed | ai-config-no-dangerous-commands |
ai-coding-session-0c60a3 |
- |
| ✅ Passed | ai-config-no-secrets |
ai-coding-session-0c60a3 |
- |
| ✅ Passed | ai-config-mcp-servers-allowed |
ai-coding-session-0c60a3 |
- |
Powered by Chainloop and Chainloop Trace
The Artifact CAS now publishes best-effort audit events to the control-plane-owned NATS JetStream stream (chainloop-audit) so a consumer can compute per-organization upload volume, download volume and deduplication savings: - New CASArtifactUploaded (with a skipped flag for deduplicated uploads) and CASArtifactDownloaded event types. - New optional nats_server section in the CAS configuration; when unset, behavior is unchanged. - The CAS publisher runs in a new publish-only mode (WithoutStreamManagement) so it never overrides the stream configuration owned by the control plane. - New SourceInternal JWT claim flags the control plane's own CAS traffic, which emits no events to keep org usage numbers clean. - Helm chart: new optional cas.nats values. Fixes #3187 Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 0c60a332-e0f1-4c2b-94ae-533467e52f5c
Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 0c60a332-e0f1-4c2b-94ae-533467e52f5c
6299c5e to
c098da7
Compare
Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 0c60a332-e0f1-4c2b-94ae-533467e52f5c
…y constructor wire cannot resolve variadic providers: the CI generate check failed because NewAuditLogPublisher's new variadic options parameter made wire demand a []auditor.PublisherOption provider in the controlplane and testhelpers injectors. Restore the original NewAuditLogPublisher signature and expose the publish-only mode as a separate wire-friendly NewPublishOnlyAuditLogPublisher constructor used directly by the CAS. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 0c60a332-e0f1-4c2b-94ae-533467e52f5c
45eac6e to
0725554
Compare
| log *log.Helper | ||
| } | ||
|
|
||
| func NewAuditDispatcher(publisher *auditor.AuditLogPublisher, logger log.Logger) *AuditDispatcher { |
There was a problem hiding this comment.
cannot it reuse the dispatcher from controlplane? perhaps moving it out of internal?
There was a problem hiding this comment.
Will run a follow up, thanks
There was a problem hiding this comment.
Good call @jiparis. I extracted the shared generate → publish → error-reporting flow into a new auditor.Dispatcher in pkg/auditor (importable, not internal), and both biz.AuditorUseCase and this CAS AuditDispatcher now delegate to it — each keeping only its own actor/org resolution. Reusing biz.AuditorUseCase directly wasn't viable since it resolves the actor from the control-plane request context and importing biz would pull the whole control-plane dependency graph into the CAS, so the shared seam lives at the lower-level dispatcher instead.
Follow-up: #3191
🤖 Posted by Maximus bot (Claude Code) on behalf of @migmartri
Closes #3187
The Artifact CAS now publishes audit events for artifact uploads, downloads and deduplicated (skipped) uploads to the existing
chainloop-auditNATS JetStream stream, so a consumer of the stream can compute per-organization upload volume, download volume and deduplication savings.Implementation overview
auditor.LogEntryformat as the rest of the audit events:CASArtifactUploadedandCASArtifactDownloaded. Events carry the organization, digest, size in bytes, filename and backend type, and are published toaudit.casartifact.*subjects.AuditDispatcherin the CAS resolves the organization from the request JWT claims and publishes through the sharedauditormachinery.nats_serversection in the CAS configuration, exposed in the Helm chart ascas.nats.*values.Major design decisions
CASArtifactUploadedevents with askipped: trueflag (no bytes were transferred or stored). Since the stored size is not known at the dedup point, it is resolved with a best-effortDescribe()call, falling back to0; consumers can join on the digest with a prior non-skipped upload.SourceInternalJWT claim is set only when the control plane mints tokens for its own CAS client (attestation storage, policy material reads). The boolean zero value means client traffic, so the client-facing token issuance paths needed no changes, and the suppression happens at emission so the stream needs no consumer-side filtering.NewPublishOnlyAuditLogPublisher), so it can never create or reconfigure thechainloop-auditstream (e.g. accidentally downgrade its replica count).nats_serveris unset the behavior is unchanged; publish failures are logged and never fail or slow down an upload/download.Consumer math
Per organization, over a time window on the stream: uploaded bytes = sum of
size_byteswhereskipped == false; downloaded bytes = sum ofCASArtifactDownloadedsizes; dedup savings = sum ofsize_byteswhereskipped == true.AI disclosure
This contribution was developed with AI assistance (Claude Code).
🤖 Posted by Maximus bot (Claude Code) on behalf of @migmartri