mailstorage

package
v0.9.2 Latest Latest
Warning

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

Go to latest
Published: Nov 6, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package mailstorage holds the per-mail-service bring-your-own-bucket core: sealed connections, key layout, probe, bucket creation and cleanup. It has no global lookup, so one service can never reach another's bucket.

Index

Constants

View Source
const AllowPrivateEnv = "APP_MAIL_STORAGE_ALLOW_PRIVATE"

AllowPrivateEnv opts in to private endpoints such as a self-hosted MinIO.

View Source
const RetentionDaysEnv = "APP_MAIL_UNINSTALL_RETENTION_DAYS"

RetentionDaysEnv is how long data survives an "after retention" uninstall.

View Source
const TimeoutEnv = "APP_MAIL_STORAGE_TIMEOUT_SECONDS"

TimeoutEnv bounds each storage operation, in seconds.

Variables

View Source
var (
	ErrBadCredentials      = errors.New("the storage endpoint rejected the access key id or secret")
	ErrBucketMissing       = errors.New("the bucket was not found at this endpoint and region")
	ErrNoDeletePermission  = errors.New("the key can write but not delete objects, so retention and uninstall cleanup would fail")
	ErrNoPermission        = errors.New("the key is not allowed to do this under the service prefix")
	ErrEndpointUnreachable = errors.New("the endpoint could not be reached or is not allowed")
)

Classified failures the dashboard explains in plain words.

View Source
var ErrDeleteNotConfirmed = errors.New("mailstorage: delete not confirmed")

ErrDeleteNotConfirmed is returned when the confirm token does not match.

View Source
var ErrKeyOutsidePrefix = errors.New("mailstorage: key is outside the service prefix")

ErrKeyOutsidePrefix is returned for any key that is not under the service prefix, including traversal and prefix-sibling tricks.

Functions

func ArchiveKey

func ArchiveKey(serviceID, mailbox string, month time.Time) string

ArchiveKey is the key of one mailbox month archive.

func BackupKey

func BackupKey(serviceID string, at time.Time) string

BackupKey is the key of one backup archive.

func ConfirmToken

func ConfirmToken(serviceID string) string

ConfirmToken is what the operator types to authorize wiping a service prefix.

func EnsureBucket

func EnsureBucket(ctx context.Context, conn Connection) error

EnsureBucket creates the bucket only when the key is allowed to, and otherwise returns a *CreateYourselfError. An existing bucket is a no-op.

func ExportKey

func ExportKey(serviceID, exportID string) string

ExportKey is the key of one export bundle.

func Guard

func Guard(serviceID, key string) error

Guard accepts only clean keys strictly inside the service prefix.

func IsR2

func IsR2(endpoint string) bool

IsR2 reports whether the endpoint is Cloudflare R2.

func LeastPrivilegePolicy

func LeastPrivilegePolicy(bucket, serviceID string) string

LeastPrivilegePolicy is the S3 policy limited to one service prefix.

func MailboxHash

func MailboxHash(address string) string

MailboxHash hides addresses in object listings.

func Prefix

func Prefix(serviceID string) string

Prefix is the only key space a service may touch.

func ProbePrefix

func ProbePrefix(serviceID string) string

ProbePrefix holds throwaway probe objects.

func R2Guidance

func R2Guidance(bucket string) string

R2Guidance explains the one-bucket-per-service rule, since R2 tokens scope to buckets and not to prefixes.

func RetentionDays

func RetentionDays() int

RetentionDays reads the uninstall retention window, default 30.

func SecretSlot

func SecretSlot(serviceID string) string

SecretSlot is the secrets-store service name for one mail service.

func ValidServiceID

func ValidServiceID(id string) bool

ValidServiceID reports whether id is safe to use as a key prefix.

Types

type Connection

type Connection struct {
	ServiceID   string `json:"service_id"`
	Endpoint    string `json:"endpoint,omitempty"`
	Region      string `json:"region,omitempty"`
	Bucket      string `json:"bucket"`
	AccessKeyID string `json:"access_key_id"`
	PathStyle   bool   `json:"path_style"`
	// contains filtered or unexported fields
}

Connection is one mail service's bucket. The secret lives only in the secrets store, under a slot derived from ServiceID, never in this value.

func Candidate

func Candidate(spec Spec) (Connection, error)

Candidate validates spec and returns a connection that holds its secret in memory only, so it can be probed before anything shared is overwritten.

func Load

func Load(store SecretStore, rec Connection) Connection

Load rebinds a stored record to the secrets store.

func New

func New(ctx context.Context, store SecretStore, spec Spec) (Connection, error)

New validates spec, seals its secret and returns the connection.

func (Connection) Forget

func (c Connection) Forget(ctx context.Context) error

Forget removes the sealed secret, for service deletion.

func (Connection) GoString

func (c Connection) GoString() string

GoString covers %#v with the same redaction.

func (Connection) LogValue

func (c Connection) LogValue() slog.Value

LogValue keeps slog output free of credentials.

func (Connection) String

func (c Connection) String() string

String never includes the secret or the key id.

type CreateYourselfError

type CreateYourselfError struct {
	Provider string
	Policy   string
	Guidance string
}

CreateYourselfError means the key may not create the bucket, so the operator must; Policy is the exact least-privilege text to apply.

func (*CreateYourselfError) Error

func (e *CreateYourselfError) Error() string

type DeleteResult

type DeleteResult struct {
	Deleted int64
	Bytes   int64
}

DeleteResult reports what cleanup removed.

func DeletePrefix

func DeletePrefix(ctx context.Context, conn Connection, confirm string, afterRetention bool) (DeleteResult, error)

DeletePrefix removes the service's objects after an uninstall. With afterRetention only objects older than RetentionDays go; otherwise all.

type ProbeResult

type ProbeResult struct {
	OK    bool   `json:"ok"`
	Steps []Step `json:"steps"`
	// Err is the classified cause of the first failure.
	Err error `json:"-"`
}

ProbeResult reports every step so a read-only token is explained plainly.

func Probe

func Probe(ctx context.Context, conn Connection) ProbeResult

Probe runs put, get, list and delete of a tiny object under the service's probe prefix. After a failure it still tries to clean up.

type SecretStore

type SecretStore interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	Resolve(ctx context.Context, serviceName, envKey string) (string, error)
	DeleteValue(ctx context.Context, serviceName, envKey string) error
}

SecretStore is the sealing surface of internal/secrets.Manager. Values are encrypted at rest and bound to the service slot they were written to.

type Spec

type Spec struct {
	ServiceID   string
	Endpoint    string
	Region      string
	Bucket      string
	AccessKeyID string
	Secret      string
	PathStyle   bool
}

Spec is the write-only input for a connection. It carries the secret once and is redacted everywhere it could be printed.

func (Spec) GoString

func (s Spec) GoString() string

GoString covers %#v with the same redaction.

func (Spec) LogValue

func (s Spec) LogValue() slog.Value

LogValue redacts the secret for slog.

func (Spec) MarshalJSON

func (s Spec) MarshalJSON() ([]byte, error)

MarshalJSON never emits the secret.

func (Spec) String

func (s Spec) String() string

String redacts the secret so a printed spec never leaks it.

type Step

type Step struct {
	Name  string `json:"name"`
	OK    bool   `json:"ok"`
	Error string `json:"error,omitempty"`
}

Step is the outcome of one probe operation.

type Store

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

Store is a bucket client that refuses every key outside its service prefix.

func Open

func Open(ctx context.Context, conn Connection) (*Store, error)

Open resolves the sealed secret and returns a prefix-guarded store.

func (*Store) Delete

func (s *Store) Delete(ctx context.Context, key string) error

Delete removes an object under the service prefix.

func (*Store) Get

func (s *Store) Get(ctx context.Context, key string) (io.ReadCloser, int64, error)

Get opens an object; the caller closes it and owns its own timeout.

func (*Store) List

func (s *Store) List(ctx context.Context, prefix, token string, limit int32) (objectstore.ListPage, error)

List lists keys under prefix, which must be inside the service prefix.

func (*Store) Put

func (s *Store) Put(ctx context.Context, key string, body io.ReadSeeker) error

Put writes an object under the service prefix.

Jump to

Keyboard shortcuts

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