s3

package
v0.0.0-...-3173d79 Latest Latest
Warning

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

Go to latest
Published: Sep 21, 2026 License: MPL-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package s3 is a minimal S3 client: PUT, GET, DELETE, HEAD and query-string (presigned) GET — the five operations amadan Releases needs and nothing else, hand-rolled per Decision Point 1 rather than adding aws-sdk-go-v2 (go.mod stays frozen at two dependencies).

It is an S3 client, not a general SigV4 signer. Two consequences:

  • Path-style addressing throughout (PUT {endpoint}/{bucket}/{key}), never virtual-hosted-style, so the same client works against AWS, MinIO, R2 or B2 unmodified — virtual-hosted style needs a wildcard DNS entry per bucket, which a self-hosted MinIO usually has not got.
  • URI paths are deliberately NOT normalized before signing. Generic SigV4 collapses "/./", "//" and ".." in the canonical request; S3 explicitly does not, because an object may genuinely be named "my-object//example//photo.user" and normalizing that path makes the request fail. See testdata/sigv4/normalize-path/normalize-path.txt, which is why TestSigV4Suite skips the six normalization cases in that group — and only those six; the group's get-space and get-special-character cases test encoding, not normalization, and do run.

AWS SigV4, both signing modes:

  • Header signing (Put/Get/Delete/Head): Authorization carries the signature, x-amz-content-sha256 carries the SHA-256 of the exact bytes sent — every call here buffers its full body first (bounded on the way out by callers, see releases_upload.go's maxAssetBytes, and on the way in by maxGetBytes), so that hash is always the real one, never "UNSIGNED-PAYLOAD".
  • Query signing (PresignGET): the signature is a URL query parameter, so the resulting URL needs no Authorization header at all — a browser's plain GET just works.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

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

func New

func New(cfg Config, hc *http.Client) *Client

New returns a client for cfg. An empty Endpoint means the bucket is an AWS-native one, so it is filled in from Region — a CARLOS store grant omits the endpoint in exactly that case.

func (*Client) Delete

func (c *Client) Delete(ctx context.Context, key string) error

func (*Client) Get

func (c *Client) Get(ctx context.Context, key string) ([]byte, error)

Get reads an object into memory, bounded by maxGetBytes. An object larger than that is an error, never a truncated slice: a caller that got half an object back and no way to tell would go on to hash it, parse it or serve it as though it were whole.

func (*Client) Head

func (c *Client) Head(ctx context.Context, key string) (size int64, exists bool, err error)

func (*Client) PresignGET

func (c *Client) PresignGET(key string, ttl time.Duration, now time.Time) (string, error)

PresignGET returns a time-limited unauthenticated URL for key. Query- string signing: SigV4's other mode, where the signature is a query parameter so a plain, unmodified http.Get (or a browser redirect) works with no Authorization header at all — releases_download.go hands one of these straight to http.Redirect.

func (*Client) PresignGETWithQuery

func (c *Client) PresignGETWithQuery(key string, extra url.Values, ttl time.Duration, now time.Time) (string, error)

PresignGETWithQuery is PresignGET with extra query parameters folded into the SIGNED query. Its reason to exist is S3's response-* header overrides: "response-content-disposition=attachment; filename=..." makes the object download rather than render, which is what the hub's release download route needs, because content_type on an asset row is the uploader's claim and an asset called "x.html" would otherwise be served as a live page from the bucket's own origin.

The extras are merged BEFORE signing, never appended after. S3 verifies the signature against the whole query string it receives, so a parameter added afterwards is not a smaller lie than a forged one — it fails the request outright.

An extra may NOT name an X-Amz-* parameter (matched case-insensitively, because that is how a query parameter name is matched): those are the signature's own machinery, and the caller would be adding a SECOND pair of the same name rather than replacing anything. X-Amz-Expires is the one that matters — it is the TTL bounding a leaked link's life, a security control — and which of two values S3 honours is not a question worth answering by experiment. Set the TTL through ttl.

func (*Client) Put

func (c *Client) Put(ctx context.Context, key string, body []byte, contentType string) error

type Config

type Config struct {
	Endpoint  string // no trailing slash, e.g. "https://s3.us-east-1.amazonaws.com"; empty means AWS in Region
	Region    string
	Bucket    string
	AccessKey string
	SecretKey string
}

Config addresses an S3-compatible endpoint. Credentials arrive here already plaintext, the same posture Config.CarlosToken already has — see Decision Point 1: the sealed-grant delivery an operator runs via `carlos store create`/`store/grant` is provisioning upstream of amadan, not code amadan runs itself.

Source Files

  • s3.go

Jump to

Keyboard shortcuts

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