config

package
v0.64.0 Latest Latest
Warning

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

Go to latest
Published: Nov 6, 2026 License: AGPL-3.0 Imports: 16 Imported by: 0

Documentation

Overview

Package config reads the configuration of every command from environment variables into one typed Config, validated as a whole at startup so a command stops before doing anything with a value it cannot use (ADR 0002).

The variables, their defaults and an example are in .env.example. A secret, such as the log key, is held as a Secret: it never appears in a log line, an error message or a formatted Config.

Commands write only under Config.DataDir, the data volume, so the rest of the file system can be mounted read-only.

Index

Constants

View Source
const (
	EnvBackupBucket          = "ARNAKOFF_BACKUP_BUCKET"
	EnvBackupBucketAccessKey = "ARNAKOFF_BACKUP_BUCKET_ACCESS_KEY"
	EnvBackupBucketSecretKey = "ARNAKOFF_BACKUP_BUCKET_SECRET_KEY" //nolint:gosec // G101: the name of a variable, not its value.
	EnvBackupRecipient       = "ARNAKOFF_BACKUP_RECIPIENT"
	EnvBackupSigningKey      = "ARNAKOFF_BACKUP_SIGNING_KEY"
)

Environment variables of the backup of the key store (#75), read by Load.

View Source
const (
	EnvAddr     = "ARNAKOFF_ADDR"
	EnvLogLevel = "ARNAKOFF_LOG_LEVEL"
	EnvLogKey   = "ARNAKOFF_LOG_KEY"
	EnvDataDir  = "ARNAKOFF_DATA_DIR"

	EnvSourceACPR = "ARNAKOFF_SOURCE_ACPR"

	EnvAlertWebhook = "ARNAKOFF_ALERT_WEBHOOK"

	EnvProblemBaseURL = "ARNAKOFF_PROBLEM_BASE_URL"

	EnvBatchMaxItems = "ARNAKOFF_BATCH_MAX_ITEMS"

	EnvInstances = "ARNAKOFF_INSTANCES"

	EnvDatabaseURL  = "ARNAKOFF_DATABASE_URL"
	EnvDatabaseCA   = "ARNAKOFF_DATABASE_CA"
	EnvAPIKeySecret = "ARNAKOFF_API_KEY_SECRET" //nolint:gosec // G101: the name of a variable, not its value.
	EnvAPIKeyEnv    = "ARNAKOFF_API_KEY_ENV"    //nolint:gosec // G101: the name of a variable, not its value.

	EnvRateLimitKey   = "ARNAKOFF_RATE_LIMIT_KEY"
	EnvRateLimitIP    = "ARNAKOFF_RATE_LIMIT_IP"
	EnvRateLimitAuth  = "ARNAKOFF_RATE_LIMIT_AUTH"
	EnvTrustedProxies = "ARNAKOFF_TRUSTED_PROXIES"
	EnvClientIPHeader = "ARNAKOFF_CLIENT_IP_HEADER"
)

Environment variables read by Load.

View Source
const (
	DefaultAddr     = ":8100"
	DefaultLogLevel = "info"
	DefaultDataDir  = "var"

	// DefaultSourceACPR keeps the ACPR blacklist off: its reuse licence is
	// still to be obtained (docs/sources.md).
	DefaultSourceACPR = "false"

	// DefaultProblemBaseURL is where arnakoff/web documents the problems
	// the API answers, one page per code.
	DefaultProblemBaseURL = "https://arnakoff.com/docs/erreurs/"
)

Defaults applied by Load to an unset variable. ARNAKOFF_LOG_KEY has none.

View Source
const (
	// DefaultBatchMaxItems is the most items POST /v1/check/batch accepts,
	// as the owner set it; the contract's example states it.
	DefaultBatchMaxItems = 50

	// MaxBatchMaxItems is the highest value Load accepts: beyond, one
	// request would weigh like a thousand checks.
	MaxBatchMaxItems = 1000
)

Bounds of ARNAKOFF_BATCH_MAX_ITEMS.

View Source
const (
	// DefaultInstances is one process: a single API, or a command run by
	// hand.
	DefaultInstances = 1

	// MaxInstances is the highest value Load accepts: beyond, each process
	// would get less than one SIRENE request a second, and a limit shared
	// among them would serve better (#336).
	MaxInstances = 7
)

Bounds of ARNAKOFF_INSTANCES.

View Source
const (
	EnvDemoOrigin          = "ARNAKOFF_DEMO_ORIGIN"
	EnvTurnstileSecret     = "ARNAKOFF_TURNSTILE_SECRET" //nolint:gosec // G101: the name of a variable, not its value.
	EnvRateLimitDemo       = "ARNAKOFF_RATE_LIMIT_DEMO"
	EnvRateLimitDemoGlobal = "ARNAKOFF_RATE_LIMIT_DEMO_GLOBAL"
)

Environment variables of the demo access, read by Load.

View Source
const (
	// DefaultRateLimitDemo is the number of demo checks of a client
	// address.
	DefaultRateLimitDemo = 10

	// DefaultRateLimitDemoGlobal is the number of demo checks of every
	// client together: a flood from many addresses stays bounded.
	DefaultRateLimitDemoGlobal = 300
)

Defaults of the demo limits, in requests per minute, as the owner set them (#60).

View Source
const (
	EnvSMTPAddr                   = "ARNAKOFF_SMTP_ADDR"
	EnvSMTPUsername               = "ARNAKOFF_SMTP_USERNAME"
	EnvSMTPPassword               = "ARNAKOFF_SMTP_PASSWORD" //nolint:gosec // G101: the name of a variable, not its value.
	EnvMailFrom                   = "ARNAKOFF_MAIL_FROM"
	EnvLoginLinkURL               = "ARNAKOFF_LOGIN_LINK_URL"
	EnvRateLimitKeyRequest        = "ARNAKOFF_RATE_LIMIT_KEY_REQUEST"
	EnvRateLimitKeyRequestAddress = "ARNAKOFF_RATE_LIMIT_KEY_REQUEST_ADDRESS"
	EnvRateLimitKeyRequestGlobal  = "ARNAKOFF_RATE_LIMIT_KEY_REQUEST_GLOBAL"
)

Environment variables of the key requests by login link, read by Load.

View Source
const (
	// DefaultMailFrom is the sending mailbox the owner chose
	// (arnakoff/.github#3).
	DefaultMailFrom = "contact@arnakoff.com"

	// DefaultLoginLinkURL is the page of the site a login link opens.
	DefaultLoginLinkURL = "https://arnakoff.com/cle"

	// DefaultRateLimitKeyRequest is the number of key requests and
	// redemptions per minute of a client address.
	DefaultRateLimitKeyRequest = 5

	// DefaultRateLimitKeyRequestAddress is the number of login links an
	// email address may receive per hour.
	DefaultRateLimitKeyRequestAddress = 3

	// DefaultRateLimitKeyRequestGlobal is the number of login links mailed
	// per hour to every address together, as the owner set it (#245,
	// 06/11/2026): a flood that solves Turnstile from many client addresses
	// spends at most 720 a day of the 10,000 emails a month of Scaleway
	// Transactional Email. It is shared among the instances (KeyRequest.Share).
	DefaultRateLimitKeyRequestGlobal = 30
)

Defaults of the key requests.

View Source
const (
	DefaultRateLimitKey = 60
	DefaultRateLimitIP  = 30

	// DefaultRateLimitAuth is the number of refused API keys a minute of
	// a client address, as the owner set it (#197).
	DefaultRateLimitAuth = 30

	// MaxRateLimit is the highest limit Load accepts.
	MaxRateLimit = 10000
)

Defaults of the rate limits, in requests per minute, as the owner set them until the offers of #80.

View Source
const (
	EnvRestoreURL      = "ARNAKOFF_RESTORE_URL"
	EnvRestoreIdentity = "ARNAKOFF_RESTORE_IDENTITY"
	EnvRestoreVerify   = "ARNAKOFF_RESTORE_VERIFY_KEY"
)

Environment variables of the restore of the key store in production (#297), read by Load.

View Source
const (
	EnvRotateAccessKey         = "ARNAKOFF_ROTATE_ACCESS_KEY"
	EnvRotateSecretKey         = "ARNAKOFF_ROTATE_SECRET_KEY"
	EnvRotateOrganizationID    = "ARNAKOFF_ROTATE_ORGANIZATION_ID"
	EnvRotateProjectID         = "ARNAKOFF_ROTATE_PROJECT_ID"
	EnvRotateBucket            = "ARNAKOFF_ROTATE_BUCKET"
	EnvRotateAppAPI            = "ARNAKOFF_ROTATE_APP_API"
	EnvRotateAppSync           = "ARNAKOFF_ROTATE_APP_SYNC"
	EnvRotateAppCI             = "ARNAKOFF_ROTATE_APP_CI"
	EnvRotateAppSelf           = "ARNAKOFF_ROTATE_APP_SELF"
	EnvRotateAppMail           = "ARNAKOFF_ROTATE_APP_MAIL"
	EnvRotateSMTPAddr          = "ARNAKOFF_ROTATE_SMTP_ADDR"
	EnvRotateSMTPUsername      = "ARNAKOFF_ROTATE_SMTP_USERNAME"
	EnvRotateAPIContainerID    = "ARNAKOFF_ROTATE_API_CONTAINER_ID"
	EnvRotateSyncAccessSecret  = "ARNAKOFF_ROTATE_SYNC_ACCESS_SECRET_ID"
	EnvRotateSyncSecretSecret  = "ARNAKOFF_ROTATE_SYNC_SECRET_SECRET_ID"
	EnvRotateSelfAccessSecret  = "ARNAKOFF_ROTATE_SELF_ACCESS_SECRET_ID"
	EnvRotateSelfSecretSecret  = "ARNAKOFF_ROTATE_SELF_SECRET_SECRET_ID"
	EnvGitHubAppClientID       = "ARNAKOFF_GITHUB_APP_CLIENT_ID"
	EnvGitHubAppInstallationID = "ARNAKOFF_GITHUB_APP_INSTALLATION_ID"
	EnvGitHubAppPrivateKey     = "ARNAKOFF_GITHUB_APP_PRIVATE_KEY"
)

Environment variables of the key rotation job (ADR 0007), read by Load.

View Source
const (
	EnvSnapshotBucket          = "ARNAKOFF_SNAPSHOT_BUCKET"
	EnvSnapshotBucketAccessKey = "ARNAKOFF_SNAPSHOT_BUCKET_ACCESS_KEY"
	EnvSnapshotBucketSecretKey = "ARNAKOFF_SNAPSHOT_BUCKET_SECRET_KEY" //nolint:gosec // G101: the name of a variable, not its value.
)

Environment variables of the snapshot bucket, read by Load.

View Source
const DefaultAPIKeyEnv = "live"

DefaultAPIKeyEnv accepts the production keys, "ark_live_…".

View Source
const EnvEraseEmail = "ARNAKOFF_ERASE_EMAIL"

EnvEraseEmail is the address of the account admin account erase -listen erases in production (#304), read by Load.

View Source
const MinLogKeyBytes = logging.MinKeyBytes

MinLogKeyBytes is the shortest log key Load accepts, once hex-decoded: the minimum of logging.New.

Variables

View Source
var ErrInvalid = errors.New("invalid configuration")

ErrInvalid means a variable holds a value Load cannot use.

Functions

This section is empty.

Types

type Backup added in v0.57.0

type Backup struct {
	// Bucket is the bucket name, as SnapshotBucket.Name.
	Bucket string

	// AccessKey and SecretKey are the credentials of the bucket, which may
	// only add objects. Both are Secrets, kept out of every log.
	AccessKey Secret
	SecretKey Secret

	// Recipient is the age public key the backups are sealed for. It is
	// public: no Secret. Its format is checked where it is used.
	Recipient string

	// SigningKey is the hex seed of the Ed25519 key that signs each dump,
	// so that a restore can tell it was made by this job (#340, point 3).
	// It is a Secret; its format is checked where it is used.
	SigningKey Secret
}

Backup is where admin backup keeps the sealed copies of the key store: a bucket out of Scaleway, and the public key they are sealed for. Every field is empty when unset; RequireBackup tells whether it is complete.

type Config

type Config struct {
	// Addr is the TCP address the HTTP API listens on, "host:port" with a
	// numeric port; an empty host means every interface.
	Addr string

	// LogLevel is the minimum level written to the log.
	LogLevel slog.Level

	// LogKey is the HMAC key that pseudonymises personal data in the log.
	LogKey Secret

	// DataDir is the only directory the commands write to: the data
	// volume. Every other path may be read-only.
	DataDir string

	// SourceACPR turns the ACPR blacklist on, once its reuse licence is
	// obtained.
	SourceACPR bool

	// AlertWebhook is the HTTPS URL a failed or rejected sync is posted
	// to; nil when unset or empty, and then alerts are only logged. It is a
	// Secret: a webhook URL usually carries its credential.
	AlertWebhook Secret

	// ProblemBaseURL is the HTTPS URL of the documentation of the API's
	// problems, ending with a slash: the type of a problem is its code
	// under it.
	ProblemBaseURL string

	// BatchMaxItems is the most items POST /v1/check/batch accepts, from 1
	// to MaxBatchMaxItems.
	BatchMaxItems int

	// Instances is the most processes of the API running at once, from 1
	// to MaxInstances. The registries limit requests per IP address, and the
	// platform does not tell whether the processes share one: each keeps to
	// its share of the limit.
	Instances int

	// DatabaseURL is the PostgreSQL connection string of the API key store;
	// nil when unset or empty. RequireKeyStore tells whether it is set.
	DatabaseURL Secret

	// DatabaseCA is the certificate authority of the database, in PEM,
	// which replaces the system's to verify it: that of a managed database
	// instance. Empty when unset. It is public: no Secret.
	DatabaseCA string

	// APIKeySecret is the HMAC key the API keys are digested with, kept
	// out of the database; nil when unset or empty.
	APIKeySecret Secret

	// APIKeyEnv is the environment of the API keys accepted: "live" or
	// "test".
	APIKeyEnv string

	// RateLimitKey is the number of requests per minute of an API key,
	// from 1 to MaxRateLimit.
	RateLimitKey int

	// RateLimitIP is the number of requests per minute of a client address
	// on the routes needing no key, from 1 to MaxRateLimit.
	RateLimitIP int

	// RateLimitAuth is the number of refused API keys per minute of a
	// client address on the check routes, from 1 to MaxRateLimit: over it,
	// its keys are no longer looked up.
	RateLimitAuth int

	// TrustedProxies are the proxies whose X-Forwarded-For field tells the
	// client's address: nil when unset, empty when set to "none".
	// RequireTrustedProxies tells whether it is set.
	TrustedProxies []netip.Prefix

	// ClientIPHeader is the field, in canonical form, that the platform in
	// front of the API overwrites with the client's address, such as
	// X-Real-Ip on Scaleway Serverless Containers; empty when unset. It
	// excludes TrustedProxies.
	ClientIPHeader string

	// Demo configures the demo access of the site's checker.
	Demo Demo

	// KeyRequest configures the free API keys requested by login link.
	KeyRequest KeyRequest

	// SnapshotBucket is the bucket the snapshots are stored in instead of
	// SnapshotDir, when it is enabled.
	SnapshotBucket SnapshotBucket

	// Backup is where admin backup keeps the sealed copies of the key
	// store.
	Backup Backup

	// Restore is the dump admin restore -listen loads in production.
	Restore Restore

	// Rotate configures the key rotation job (ADR 0007).
	Rotate Rotate

	// Erase is the account admin account erase erases (#304).
	Erase Erase
}

Config is the configuration of every command.

func Load

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

Load reads the configuration with lookup, such as os.LookupEnv. A variable that is set, even to an empty string, is used as is; an unset one takes its default. It returns every invalid variable at once, each wrapping ErrInvalid; an error never quotes the value of a Secret.

func (Config) RequireBackup added in v0.57.0

func (c Config) RequireBackup() error

RequireBackup returns an error, wrapping ErrInvalid and naming each missing variable, unless the database and the backup are configured.

func (Config) RequireDatabase added in v0.39.0

func (c Config) RequireDatabase() error

RequireDatabase returns an error, wrapping ErrInvalid, unless the database of the API key store is set.

func (Config) RequireErase added in v0.60.0

func (c Config) RequireErase() error

RequireErase returns an error, wrapping ErrInvalid and naming each missing variable, unless the database and the address to erase are configured.

func (Config) RequireFileRestore added in v0.63.0

func (c Config) RequireFileRestore() error

RequireFileRestore returns an error, wrapping ErrInvalid and naming each missing variable, unless the database and the verify key of a clear dump are configured.

func (Config) RequireKeyStore added in v0.39.0

func (c Config) RequireKeyStore() error

RequireKeyStore returns an error, wrapping ErrInvalid, unless the API key store is configured: a database and an API key secret other than the log key, so that a leaked log key does not also let anyone check a key.

func (Config) RequireRestore added in v0.58.0

func (c Config) RequireRestore() error

RequireRestore returns an error, wrapping ErrInvalid and naming each missing variable, unless the database and the restore are configured.

func (Config) RequireRotate added in v0.59.0

func (c Config) RequireRotate() error

RequireRotate returns an error, wrapping ErrInvalid and naming each missing variable, unless the rotation job is configured.

func (Config) RequireTrustedProxies added in v0.40.0

func (c Config) RequireTrustedProxies() error

RequireTrustedProxies returns an error, wrapping ErrInvalid, when the API serves live keys without ARNAKOFF_TRUSTED_PROXIES nor ARNAKOFF_CLIENT_IP_HEADER: the operator must say who tells the client's address, or that no proxy does.

func (Config) SnapshotDir

func (c Config) SnapshotDir() string

SnapshotDir is where the validated snapshots of the sources are stored, inside DataDir, unless SnapshotBucket is enabled.

type Demo added in v0.42.0

type Demo struct {
	// Origin is the site's origin, "https://host", the only one CORS
	// allows; empty when the demo is off.
	Origin string

	// TurnstileSecret is the secret key of the site's Turnstile widget;
	// nil when the demo is off.
	TurnstileSecret Secret

	// RateLimit is the number of demo checks per minute of a client
	// address, from 1 to MaxRateLimit.
	RateLimit int

	// RateLimitGlobal is the number of demo checks per minute of every
	// client together, from 1 to MaxRateLimit.
	RateLimitGlobal int
}

Demo configures the demo access of the site's checker: checks without an API key, from the site only, each proven by a Cloudflare Turnstile token.

func (Demo) Enabled added in v0.42.0

func (d Demo) Enabled() bool

Enabled reports whether the demo access is on.

func (Demo) Hostname added in v0.42.0

func (d Demo) Hostname() string

Hostname returns the host of Origin, without a port: the hostname a valid Turnstile token names.

type Erase added in v0.60.0

type Erase struct {
	// Email is the address of the account: personal data, a Secret so
	// that no log shows it; its format is checked where it is used.
	Email Secret
}

Erase is the account admin account erase erases.

type GitHubApp added in v0.59.0

type GitHubApp struct {
	ClientID       string
	InstallationID int64
	// PrivateKey is its private key, in PEM, checked where it is used.
	PrivateKey Secret
}

GitHubApp is the GitHub App the job writes environment secrets as.

type KeyRequest added in v0.52.0

type KeyRequest struct {
	// SMTPAddr is the "host:port" of the sending server, which speaks TLS
	// at once; empty when the key requests are off.
	SMTPAddr string

	// SMTPUsername and SMTPPassword authenticate with the server.
	SMTPUsername string
	SMTPPassword Secret

	// MailFrom is the sender address, a bare address.
	MailFrom string

	// LinkURL is the HTTPS page of the site a login link opens, the token
	// in its fragment.
	LinkURL string

	// RateLimit is the number of requests per minute of a client address
	// to the key request routes, from 1 to MaxRateLimit.
	RateLimit int

	// RateLimitAddress is the number of login links per hour an email
	// address may receive, from 1 to MaxRateLimit.
	RateLimitAddress int

	// RateLimitGlobal is the number of login links per hour mailed to
	// every address together, from 1 to MaxRateLimit.
	RateLimitGlobal int
}

KeyRequest configures the free API keys requested by login link (#59).

func (KeyRequest) Enabled added in v0.52.0

func (k KeyRequest) Enabled() bool

Enabled reports whether the key requests are on.

func (KeyRequest) Share added in v0.62.2

func (k KeyRequest) Share(instances int) KeyRequest

Share returns k with the mail limits split among instances processes running at once, each counting in its own memory: together they mail no more than the configured hourly figures (#344). Each share is at least one. The per-minute limit of a client address is left whole, as every other per-address limit.

type Restore added in v0.58.0

type Restore struct {
	// URL is the presigned URL of the sealed dump: its query is a
	// credential.
	URL Secret

	// Identity is the age private key the dump is sealed for, drawn for
	// this restore only.
	Identity Secret

	// VerifyKey is the hex Ed25519 public key that checks the signature of
	// the dump, made by the backup job. It is public: no Secret.
	VerifyKey string
}

Restore is the dump admin restore -listen loads: a presigned URL of the dump, sealed for a key drawn for this restore, and that key. Both are Secrets, kept out of every log; their format is checked where they are used. RequireRestore tells whether it is complete.

type Rotate added in v0.59.0

type Rotate struct {
	// AccessKey and SecretKey are the key of the job itself.
	AccessKey string
	SecretKey Secret

	// OrganizationID is the organization of the applications, which the
	// API requires to list their keys; ProjectID is the default project of
	// the keys it creates.
	OrganizationID string
	ProjectID      string

	// Bucket is the snapshot bucket a new key of api or sync must read.
	Bucket string

	// Apps are the IAM applications of the closed list.
	Apps RotateApps

	// APIContainerID is the api container, whose secrets hold its key.
	APIContainerID string

	// SyncSecrets and SelfSecrets are the Secret Manager secrets the sync
	// job and the rotation job read their key from, one per half.
	SyncSecrets, SelfSecrets SecretIDs

	// GitHubApp writes the key of ci-deploy to the GitHub environment.
	GitHubApp GitHubApp

	// SMTP is the server a new key of mail must authenticate with.
	SMTP RotateSMTP
}

Rotate is the configuration of the key rotation job: its own Scaleway key, the closed list of applications whose keys it rotates and where it puts them. Every field is empty when unset; RequireRotate tells whether it is complete. Identifiers are UUIDs, checked by Load.

type RotateApps added in v0.59.0

type RotateApps struct {
	API, Sync, CI, Self, Mail string
}

RotateApps are the IAM applications whose keys the job rotates.

type RotateSMTP added in v0.60.1

type RotateSMTP struct {
	Addr, Username string
}

RotateSMTP is the SMTP server of the mail application (#295): its "host:port", which speaks TLS at once, and the username its keys authenticate as.

type Secret

type Secret []byte

Secret is a configuration value that must never reach a log, an error or a response. Every fmt verb, encoding/json and log/slog write it as "[redacted]"; only Bytes gives the value.

func (Secret) Bytes

func (s Secret) Bytes() []byte

Bytes returns a copy of the secret value.

func (Secret) Format

func (s Secret) Format(f fmt.State, _ rune)

Format implements fmt.Formatter: every verb writes "[redacted]".

func (Secret) LogValue

func (s Secret) LogValue() slog.Value

LogValue implements slog.LogValuer.

func (Secret) MarshalText

func (s Secret) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler, which encoding/json uses.

type SecretIDs added in v0.59.0

type SecretIDs struct {
	Access, Secret string
}

SecretIDs are the two secrets of a key: its access key and its secret key.

type SnapshotBucket added in v0.50.0

type SnapshotBucket struct {
	// Name is the bucket: 3 to 63 lower-case letters, digits and dashes,
	// starting and ending with a letter or a digit.
	Name string

	// AccessKey and SecretKey are the credentials of the bucket: the key
	// pair of an IAM application restricted to it. Both are Secrets, kept
	// out of every log.
	AccessKey Secret
	SecretKey Secret
}

SnapshotBucket is the S3 bucket the snapshots are stored in, in production (ADR 0005); off when its name is empty, and then they stay in SnapshotDir.

func (SnapshotBucket) Enabled added in v0.50.0

func (b SnapshotBucket) Enabled() bool

Enabled reports whether the snapshots are stored in the bucket.

Jump to

Keyboard shortcuts

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