Skip to content

bug: CLI falls back to inline storage #3133

@jiparis

Description

@jiparis

Regression: GetUploadCreds fails with "org id is required"

Summary

After PR #3121 (commit 21c5b930, merged 2026-05-18) the AttestationService.GetUploadCreds RPC fails for every non-inline CAS backend with:

ERROR  {"component": "service", "msg": "org id is required"}

The CLI silently falls back to inline attestation storage:

WRN failed to get CAS credentials, will store inline
    error="rpc error: code = Internal desc = server error"

So artifacts that should land in the configured CAS backend (OCI, S3, Azure Blob, S3 Access Point) end up embedded inside the attestation envelope instead. The keyless signing flow itself is unaffected — this only impacts the CAS upload credentials path.

Root cause

PR #3121 made OrgID a hard requirement on every CAS JWT:

  • app/controlplane/pkg/biz/cascredentials.go:60-64

    func (uc *CASCredentialsUseCase) GenerateTemporaryCredentials(backendRef *CASCredsOpts) (string, error) {
        if backendRef.OrgID == uuid.Nil {
            return "", fmt.Errorf("org id is required")
        }
        ...
    }
  • The call site was updated to pass backend.OrganizationID:
    app/controlplane/internal/service/attestation.go:497

    ref := &biz.CASCredsOpts{
        BackendType: string(backend.Provider),
        SecretPath:  backend.SecretName,
        Role:        casJWT.Uploader,
        MaxBytes:    backend.Limits.MaxBytes,
        OrgID:       backend.OrganizationID, // <- new
    }
  • backend.OrganizationID is populated only when the Organization edge is
    eager-loaded on the ent record:
    app/controlplane/pkg/data/casbackend.go:411-413

    if org := backend.Edges.Organization; org != nil {
        r.OrganizationID = org.ID
    }
  • Every direct CAS backend loader in casbackend.go chains
    .WithOrganization() — but the workflow run's eager loader does not:
    app/controlplane/pkg/data/workflowrun.go:169-175

    func eagerLoadWorkflowRun(client *ent.Client) *ent.WorkflowRunQuery {
        return client.WorkflowRun.Query().
            WithWorkflow(func(q *ent.WorkflowQuery) { q.WithOrganization().WithProject() }).
            WithVersion().
            WithContractVersion().
            WithCasBackends()              // <- missing WithOrganization
    }

Since GetUploadCreds reads the backend via
wRun.CASBackends[0]
(attestation.go:481),
OrganizationID is always uuid.Nil on that path, so the OrgID check trips
on every call.

Impact

  • Affected: every workflow run that uses a CAS backend with credentials
    (backend.SecretName != ""). That includes OCI, S3, Azure Blob, and the
    new S3 Access Point backend.
  • Not affected: inline-only setups (no SecretName) — these skip the JWT
    generation block entirely.
  • Severity: silent functional regression. The CLI's fallback to inline
    storage hides the failure unless you read the control plane logs or
    notice attestations growing unexpectedly large. No data loss, but the
    configured CAS backend is effectively bypassed.

Reproduction

  1. Configure a CAS backend with credentials (OCI / S3 / Azure / S3 Access
    Point).
  2. Run an attestation push against a workflow on that backend.
  3. Observe the control plane log:
    ERROR {"component": "service", "msg": "org id is required"}
    at service.handleUseCaseErr (service.go:423) called from
    (*AttestationService).GetUploadCreds (attestation.go:500).
  4. The CLI logs WRN failed to get CAS credentials, will store inline.

References

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions