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 ¶
- type Client
- func (c *Client) Delete(ctx context.Context, key string) error
- func (c *Client) Get(ctx context.Context, key string) ([]byte, error)
- func (c *Client) Head(ctx context.Context, key string) (size int64, exists bool, err error)
- func (c *Client) PresignGET(key string, ttl time.Duration, now time.Time) (string, error)
- func (c *Client) PresignGETWithQuery(key string, extra url.Values, ttl time.Duration, now time.Time) (string, error)
- func (c *Client) Put(ctx context.Context, key string, body []byte, contentType string) error
- type Config
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 ¶
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) Get ¶
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 (*Client) PresignGET ¶
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.
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