diff --git a/Dockerfile b/Dockerfile index 3eb5782..27ab84b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -67,8 +67,9 @@ RUN adduser -D -H -s /sbin/nologin pixad && \ mkdir -p /var/lib/pixa /etc/pixa && \ chown pixad:pixad /var/lib/pixa -# Copy default config (edit signing_key before use) -COPY config.example.yml /etc/pixa/config.yml +# Copy the image config; signing_key comes from PIXA_SIGNING_KEY. +# Mount a file over /etc/pixa/config.yml to override anything else. +COPY config.docker.yml /etc/pixa/config.yml USER pixad WORKDIR /var/lib/pixa diff --git a/README.md b/README.md index 8e36dc0..32fed7f 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,25 @@ git clone https://git.eeqj.de/sneak/pixa.git cd pixa make build -# run with a config file -./bin/pixad --config config.example.yml +# run with a config file: copy the example and set a real signing key +# (the example placeholder is refused at startup), e.g. with +# openssl rand -base64 32 +cp config.example.yml config.yml +$EDITOR config.yml # replace the signing_key placeholder +./bin/pixad --config config.yml # or build and run via Docker make docker -docker run -p 8080:8080 pixad:latest +docker run -p 8080:8080 -e PIXA_SIGNING_KEY="$(openssl rand -base64 32)" pixa:latest ``` +A container is configured two ways. The signing key comes from the +`PIXA_SIGNING_KEY` environment variable, which the baked-in config +reads; if it is unset the container exits at startup naming the +variable. Everything else uses built-in defaults, so to change any +other setting mount your own file over `/etc/pixa/config.yml` (see +`config.example.yml` for the full set of keys). + ## Rationale Image-heavy web applications need a fast, caching reverse proxy that diff --git a/config.docker.yml b/config.docker.yml new file mode 100644 index 0000000..548b45a --- /dev/null +++ b/config.docker.yml @@ -0,0 +1,11 @@ +# Pixa configuration baked into the Docker image. +# +# The signing key is read from the PIXA_SIGNING_KEY environment +# variable; startup aborts naming it when it is unset. Every other key +# is omitted so its default applies. Operators who need more (an +# allowlist, metrics, and so on) mount their own file over +# /etc/pixa/config.yml. + +signing_key: "${ENV:PIXA_SIGNING_KEY}" +state_dir: /var/lib/pixa +port: 8080 diff --git a/internal/config/config.go b/internal/config/config.go index fb6e02d..e3ab570 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -44,6 +44,12 @@ const ( keyCacheMaxBytes = "cache_max_bytes" ) +// placeholderSigningKey is the dummy signing_key shipped in +// config.example.yml. It is 45 characters, so it passes the length +// check, but it is public in this repository and must be rejected at +// startup so no deployment ever signs URLs with it. +const placeholderSigningKey = "CHANGE_ME_generate_with_openssl_rand_base64_32" + // Static validation errors. Each use site attaches the offending key // and value by wrapping these with fmt.Errorf and %w. var ( @@ -61,6 +67,9 @@ var ( errPortOutOfRange = errors.New("outside the valid port range") errTooFewConnections = errors.New("must be at least 1") errValueTooShort = errors.New("value too short") + errPlaceholderKey = errors.New( + "is the placeholder from config.example.yml; " + + "generate a real key with: openssl rand -base64 32") errMustBeSetTogether = errors.New("must be set together") errMustNotBeNegative = errors.New("must not be negative") errOverflowsInt64 = errors.New("overflows a 64-bit integer") @@ -341,10 +350,10 @@ func (c *Config) ensureStateDirWritable() error { return nil } -// validate checks that all required configuration values are set and -// that every value is within its valid range. -func (c *Config) validate() error { - // The signing key value is never echoed in error messages. +// validateSigningKey checks that the signing key is present, long +// enough, and not the public placeholder from config.example.yml. The +// key value itself is never echoed in error messages. +func (c *Config) validateSigningKey() error { if c.SigningKey == "" { return fmt.Errorf("config key %q: %w", keySigningKey, errValueRequired) } @@ -356,6 +365,21 @@ func (c *Config) validate() error { keySigningKey, errValueTooShort, minKeyLength, len(c.SigningKey)) } + if c.SigningKey == placeholderSigningKey { + return fmt.Errorf("config key %q: %w", keySigningKey, errPlaceholderKey) + } + + return nil +} + +// validate checks that all required configuration values are set and +// that every value is within its valid range. +func (c *Config) validate() error { + err := c.validateSigningKey() + if err != nil { + return err + } + const maxPort = 65535 if c.Port < 1 || c.Port > maxPort { return fmt.Errorf("config key %q: value %d is %w 1-%d", diff --git a/internal/config/config_validation_internal_test.go b/internal/config/config_validation_internal_test.go index ca1573e..c12d27d 100644 --- a/internal/config/config_validation_internal_test.go +++ b/internal/config/config_validation_internal_test.go @@ -303,6 +303,11 @@ func invalidHostAndCredentialCases() []abortCase { yaml: "signing_key: short\n", wantErrSubstrings: []string{keySigningKey}, }, + { + name: "signing_key is the documented placeholder", + yaml: "signing_key: " + placeholderSigningKey + "\n", + wantErrSubstrings: []string{keySigningKey}, + }, { name: "signing_key missing", yaml: "port: 8080\n",