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
- Variables
- func ArchiveKey(serviceID, mailbox string, month time.Time) string
- func BackupKey(serviceID string, at time.Time) string
- func ConfirmToken(serviceID string) string
- func EnsureBucket(ctx context.Context, conn Connection) error
- func ExportKey(serviceID, exportID string) string
- func Guard(serviceID, key string) error
- func IsR2(endpoint string) bool
- func LeastPrivilegePolicy(bucket, serviceID string) string
- func MailboxHash(address string) string
- func Prefix(serviceID string) string
- func ProbePrefix(serviceID string) string
- func R2Guidance(bucket string) string
- func RetentionDays() int
- func SecretSlot(serviceID string) string
- func ValidServiceID(id string) bool
- type Connection
- type CreateYourselfError
- type DeleteResult
- type ProbeResult
- type SecretStore
- type Spec
- type Step
- type Store
- func (s *Store) Delete(ctx context.Context, key string) error
- func (s *Store) Get(ctx context.Context, key string) (io.ReadCloser, int64, error)
- func (s *Store) List(ctx context.Context, prefix, token string, limit int32) (objectstore.ListPage, error)
- func (s *Store) Put(ctx context.Context, key string, body io.ReadSeeker) error
Constants ¶
const AllowPrivateEnv = "APP_MAIL_STORAGE_ALLOW_PRIVATE"
AllowPrivateEnv opts in to private endpoints such as a self-hosted MinIO.
const RetentionDaysEnv = "APP_MAIL_UNINSTALL_RETENTION_DAYS"
RetentionDaysEnv is how long data survives an "after retention" uninstall.
const TimeoutEnv = "APP_MAIL_STORAGE_TIMEOUT_SECONDS"
TimeoutEnv bounds each storage operation, in seconds.
Variables ¶
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.
var ErrDeleteNotConfirmed = errors.New("mailstorage: delete not confirmed")
ErrDeleteNotConfirmed is returned when the confirm token does not match.
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 ¶
ArchiveKey is the key of one mailbox month archive.
func ConfirmToken ¶
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 LeastPrivilegePolicy ¶
LeastPrivilegePolicy is the S3 policy limited to one service prefix.
func MailboxHash ¶
MailboxHash hides addresses in object listings.
func ProbePrefix ¶
ProbePrefix holds throwaway probe objects.
func R2Guidance ¶
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 ¶
SecretSlot is the secrets-store service name for one mail service.
func ValidServiceID ¶
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 ¶
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 ¶
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) MarshalJSON ¶
MarshalJSON never emits the secret.
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.