mailcert

package
v0.9.0 Latest Latest
Warning

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

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

Documentation

Overview

Package mailcert checks, validates and installs the TLS certificate the instance-owned mail server serves (ADR 062). It holds no transport of its own: the API supplies the volume, the restart and the probe.

Index

Constants

View Source
const (
	OutcomeInstalled      = "installed"
	OutcomeRolledBack     = "rolled_back"
	OutcomeRollbackFailed = "rollback_failed"
	OutcomeFailed         = "failed"
)

Outcome values for Result.Outcome.

View Source
const (
	PhaseStaging  = "staging"
	PhaseSwapping = "swapping"
)

Journal phases: while staging the live pair is untouched, from swapping on it may hold the new pair.

View Source
const (
	CertPath     = "certs/managed.pem"
	KeyPath      = "certs/managed.key"
	PrevCertPath = "certs/managed.prev.pem"
	PrevKeyPath  = "certs/managed.prev.key"
	NewCertPath  = "certs/managed.new.pem"
	NewKeyPath   = "certs/managed.new.key"
	JournalPath  = "certs/managed.journal"
)

Paths inside the mail config volume, relative to the directory mox reads its config from.

View Source
const (
	TemplateID   = "mail-mox"
	ConfigVolume = "mail_config"
)

TemplateID and ConfigVolume name the mail template and the volume holding the managed pair.

View Source
const SubmissionTLSPort = 465

SubmissionTLSPort is the implicit TLS submission port probed for the served certificate.

Variables

View Source
var ErrACMENotImplemented = errors.New("mail cert: obtaining a certificate through ACME is not yet implemented, upload one instead")

ErrACMENotImplemented is the seam for issuing through the ingress ACME (ADR 062, option A).

View Source
var ErrInstallInProgress = errors.New("mail cert: another certificate install is in progress, retry when it finishes")

ErrInstallInProgress means a journal from another install is still inside its lease.

View Source
var ErrInterrupted = errors.New("mail cert: an earlier install did not finish")

ErrInterrupted is the cause recorded when an earlier install did not finish.

View Source
var ErrKeyModeTooWide = errors.New("mail cert: the node did not write the key with mode 0600, upgrade the node agent")

ErrKeyModeTooWide means the node wrote the key wider than 0600, usually an agent without the private write option.

View Source
var ErrKeyRemoveFailed = errors.New("mail cert: the node wrote the key wider than 0600 and deleting it failed, so the key may be readable by other users of the node: delete it by hand and upgrade the node agent")

ErrKeyRemoveFailed means a key landed wider than 0600 and could not be deleted again.

View Source
var ErrNotPrivateTarget = errors.New("mail cert: the probe target is not a private address")

ErrNotPrivateTarget means the probe address is not a private or loopback address.

View Source
var ErrPEMTooLarge = errors.New("mail cert: the certificate chain or the key is larger than " + envMaxPEMKB + " allows")

ErrPEMTooLarge means one uploaded PEM field is over the configured size.

Functions

func BackupExcludes

func BackupExcludes(templateID, volumeName string) []string

BackupExcludes returns the tar patterns a generic backup of this volume must leave out: the managed keys and any temp file that may hold one.

func IsProtectedPath

func IsProtectedPath(rel string) bool

IsProtectedPath reports whether rel, a cleaned volume path, is a managed pair file, its rollback, staging or journal, or an upload temp file beside them. Case is folded so a case-insensitive file system cannot bypass it.

Types

type Config

type Config struct {
	Window        Window
	ProbeTimeout  time.Duration
	VerifyTimeout time.Duration
	PollInterval  time.Duration
	MaxPEMBytes   int64
	// RollbackTimeout bounds a rollback on its own context, detached from the request.
	RollbackTimeout time.Duration
	// InstallLease is how long a journal blocks other installs before it counts as interrupted.
	InstallLease time.Duration
}

Config holds the env driven knobs; defaults live here and nowhere else.

func ConfigFromEnv

func ConfigFromEnv(lookup func(string) (string, bool)) (Config, error)

ConfigFromEnv reads Config through lookup, keeping the defaults for unset or invalid values.

func (Config) CheckSize

func (c Config) CheckSize(certPEM, keyPEM []byte) error

CheckSize rejects a chain or key over MaxPEMBytes before anything is parsed or sealed.

type Info

type Info struct {
	Subject    string
	Issuer     string
	NotAfter   time.Time
	Serial     string
	SHA256     string
	SelfSigned bool
}

Info is the public description of one certificate: never any key material.

func Probe

func Probe(ctx context.Context, ip string, port int, timeout time.Duration) (Info, error)

Probe dials ip:port with implicit TLS and describes the served leaf. Only private or loopback targets are allowed and no credentials are sent.

func Validate

func Validate(certPEM, keyPEM []byte, host string, now time.Time) (Info, error)

Validate checks an uploaded pair: only certificates in the chain, one unencrypted key that matches the leaf, the leaf valid now and covering host.

func (Info) DaysLeft

func (i Info) DaysLeft(now time.Time) int

DaysLeft is the whole days from now until NotAfter, negative once expired.

type Journal

type Journal struct {
	Phase     string    `json:"phase"`
	StartedAt time.Time `json:"started_at"`
	NewSHA256 string    `json:"new_sha256"`
	HadPrev   bool      `json:"had_prev"`
	Origin    Origin    `json:"origin"`
}

Journal is the persisted in-progress marker of one install. It holds no key material.

func ReadJournal

func ReadJournal(ctx context.Context, vol Volume) (Journal, bool, error)

ReadJournal returns the journal of an unfinished install, if any.

func (Journal) Stale

func (j Journal) Stale(cfg Config, now time.Time) bool

Stale reports whether the install that wrote j is past its lease and so cannot still be running.

type Origin

type Origin struct {
	CertFile string `json:"cert_file,omitempty"`
	KeyFile  string `json:"key_file,omitempty"`
	Set      bool   `json:"set"`
}

Origin is the certificate setting the mail app had before an install, so a rollback can restore it.

type Result

type Result struct {
	Outcome string
	Reason  string
	Served  Info
	HadPrev bool
}

Result reports an install. Reason is a short code, never key material.

func Install

func Install(ctx context.Context, cfg Config, vol Volume, sw Switcher, certPEM, keyPEM []byte, want Info, orig Origin) (Result, error)

Install stages the pair under temporary names and checks it, keeps the previous managed pair as rollback, swaps the live pair, restarts mox and waits until it serves want. A journal marks the install until it ends, so an interrupted one is found by Recover. Rollback uses its own bounded context so a spent install deadline cannot skip it.

func Recover

func Recover(ctx context.Context, cfg Config, vol Volume, sw Switcher, j Journal) (Result, error)

Recover finishes an install found through its journal. A pair that is already served and on disk is kept; anything else goes back to the previous pair and setting. A nil error means the volume is consistent again.

type Switcher

type Switcher interface {
	Activate(ctx context.Context) error
	Revert(ctx context.Context) error
	Served(ctx context.Context) (Info, error)
}

Switcher points mox at the managed files and restarts it, or undoes that.

type Volume

type Volume interface {
	// Read returns the file, or found false when it does not exist.
	Read(ctx context.Context, path string) (data []byte, found bool, err error)
	Write(ctx context.Context, path string, data []byte, private bool) error
	Mode(ctx context.Context, path string) (fs.FileMode, error)
	Remove(ctx context.Context, path string) error
}

Volume is the mail config volume as the installer needs it. Remove of a missing file is not an error.

type Window

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

Window is a daily UTC quiet window "HH:MM-HH:MM"; the zero Window allows any time.

func ParseWindow

func ParseWindow(s string) (Window, error)

ParseWindow parses "HH:MM-HH:MM" (UTC); an empty string is the zero Window.

func (Window) Contains

func (w Window) Contains(t time.Time) bool

Contains reports whether t falls inside the window; a window may wrap past midnight.

func (Window) String

func (w Window) String() string

String renders the window as configured, or "any time".

Jump to

Keyboard shortcuts

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