s3accesspoint

package
v1.98.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 19, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Package s3accesspoint implements a CAS backend that targets a single AWS S3 Access Point per tenant. Multiple tenants share one physical bucket; per-tenant isolation is provided by:

  1. The Access Point's resource policy, which gates who can address the AP and may further restrict s3:prefix.
  2. A per-request sts:AssumeRole that mints a scoped session whose RoleSessionName is derived from the authenticated requesting org. The AP's resource policy enforces a StringEquals on aws:userid so that a session minted for org A cannot read or write to org B's AP — even if org A's secret blob has been tampered with to point at org B's ARN.
  3. A per-tenant key prefix derived from the requesting org UUID: every object is keyed as <orgUUID>/sha256:<digest> and the AssumeRole session policy's Resource is scoped to ${apARN}/object/<orgUUID>/*. The prefix shares its source of truth with the session name, so a tampered secret cannot reroute a tenant's writes into a different namespace.

The session name MUST come from the request context, not from the secret blob: a secrets-store compromise alone must not let an attacker reroute uploads to another tenant's AP.

Index

Constants

View Source
const DevModeEnvVar = "CHAINLOOP_S3_ACCESS_POINT_DEV_MODE"

DevModeEnvVar when set to a truthy value, short-circuits sts:AssumeRole and routes S3 calls through whatever ambient AWS identity the SDK's default credential chain produced (env vars, ~/.aws/credentials, instance profile, IRSA, …). The fail-closed check on a missing requesting-org context is still enforced.

DEV ONLY. This bypasses the per-tenant isolation guarantees that the AssumeRole + session-policy + AP-policy chain provides; objects addressed via this backend are limited only by whatever the developer's IAM identity allows. NEVER set this in a multi-tenant deployment.

View Source
const ProviderID = "AWS-S3-ACCESS-POINT"

ProviderID is the stable identifier used by the CASBackend table's enum and by every other place that needs to disambiguate this provider from the regular s3 one.

View Source
const SessionDuration = time.Hour

SessionDuration is the STS token lifetime. STS allows up to 12h; 1h keeps blast radius of a leaked token small while still giving the credential cache useful reuse across consecutive uploads.

Variables

View Source
var ErrMissingRequestingOrg = errors.New("s3accesspoint: requesting org missing from claims")

ErrMissingRequestingOrg is returned when a request reaches the backend without an org UUID in its context. The backend fails closed in this case rather than minting a session with a default/empty name that would be useless against an AP policy condition.

Functions

This section is empty.

Types

type Backend

type Backend struct {
	// contains filtered or unexported fields
}

Backend is the per-tenant uploader/downloader. One *Backend instance is bound to one access point; the actual AWS credentials are minted per-request via STS using the org UUID found in the request context.

func NewBackend

func NewBackend(ctx context.Context, creds *Credentials) (*Backend, error)

NewBackend constructs a *Backend wired to an STS-backed credentials provider. ctx is used only for the initial AWS config load (DNS lookups, IMDS, IRSA token reads); it is not retained for later operations.

func (*Backend) CheckWritePermissions

func (b *Backend) CheckWritePermissions(ctx context.Context) error

CheckWritePermissions verifies that the calling org can actually mint a scoped session and put/get an object through its AP. Unlike the regular s3 backend's variant this MUST be invoked with a context carrying the org

func (*Backend) Describe

func (b *Backend) Describe(ctx context.Context, digest string) (*pb.CASResource, error)

func (*Backend) Download

func (b *Backend) Download(ctx context.Context, w io.Writer, digest string) error

func (*Backend) Exists

func (b *Backend) Exists(ctx context.Context, digest string) (bool, error)

func (*Backend) Upload

func (b *Backend) Upload(ctx context.Context, r io.Reader, resource *pb.CASResource) error

type BackendProvider

type BackendProvider struct {
	// contains filtered or unexported fields
}

BackendProvider implements backend.Provider for the access-point-backed managed CAS. Construction takes only the credentials reader; everything the provider needs at request time lives in the per-tenant secret blob.

func NewBackendProvider

func NewBackendProvider(cReader credentials.Reader) *BackendProvider

NewBackendProvider constructs the provider. A nil credentials reader is a programmer error and surfaces as a startup failure.

func (*BackendProvider) FromCredentials

func (p *BackendProvider) FromCredentials(ctx context.Context, secretName string) (backend.UploaderDownloader, error)

FromCredentials reads the per-tenant Credentials blob from the secrets manager and constructs a *Backend bound to that tenant's AP.

The returned UploaderDownloader is safe to reuse across requests; each request must enrich its context with org claim so the STS-minted session name matches the AP's resource-policy condition.

func (*BackendProvider) ID

func (p *BackendProvider) ID() string

func (*BackendProvider) ValidateAndExtractCredentials

func (p *BackendProvider) ValidateAndExtractCredentials(location string, credsJSON []byte) (any, error)

ValidateAndExtractCredentials decodes credsJSON into a Credentials struct and optionally cross-checks it against the location passed by the caller. This is invoked when a managed row is being created or revalidated; the returned value is what gets persisted in the secrets manager by upstream callers.

Unlike the regular s3 provider, this does NOT exercise live S3 permissions during validation: the credentials by themselves can't be tested without a request-context org UUID, so a proper end-to-end check belongs in the upload path. PerformValidation in the controlplane still calls this method for managed rows; it will succeed as long as the blob is well-formed.

type Credentials

type Credentials struct {
	// AccessPointARN, e.g.
	//   arn:aws:s3:us-east-1:123456789012:accesspoint/chainloop-org-<uuid>
	// The provider passes this string verbatim as the Bucket parameter on
	// every S3 SDK call.
	AccessPointARN string
	// Region the AP lives in.
	Region string
	// BaseRoleARN is the IAM role assumed via STS to mint per-request,
	// per-tenant scoped credentials. Stored per-tenant (not per-deployment)
	// so a single chainloop install can serve tenants across multiple AWS
	// accounts without a config change. Required unless DevModeEnvVar is
	// set on the running binary.
	BaseRoleARN string
}

Credentials is the per-tenant blob stashed in the secrets manager under CASBackend.SecretName. Despite the name it carries no access keys — only tenant-identifying coordinates used to construct a scoped S3 client.

The per-tenant key prefix is intentionally NOT a field here: it's derived at request time from the authenticated requesting org carried in ctx via org claim. Both the bucket-layer key namespace and the AssumeRole session-name binding therefore come from the same untamperable source, so a secrets-store compromise that rewrites this blob still can't reroute a tenant's writes into another tenant's namespace.

func (*Credentials) Validate

func (c *Credentials) Validate() error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL