Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,11 @@ jobs:
origin=$(docker inspect -f \
'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' dims-origin)
docker rm -f dims-ci
docker run -d --name dims-ci -e DIMS_WHITELIST="$origin" mod-dims:ci
# The origin runs on a private network address, so this run opts in.
docker run -d --name dims-ci \
-e DIMS_WHITELIST="$origin" \
-e DIMS_ALLOW_PRIVATE_ADDRESSES=on \
mod-dims:ci
sleep 5
docker exec dims-ci curl -fsS -o /tmp/out.png \
"http://127.0.0.1:8000/dims3/development/resize/100x100/?url=http://$origin/grid.png"
Expand Down
54 changes: 47 additions & 7 deletions conf/mod_dims.conf.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
#
# Installing the module does not edit httpd.conf. That is deliberate: a build
# step must not change a running server's configuration.
#
# The settings below are the secure ones. Each note says what it protects and
# when to relax it.

<IfModule !mod_dims.c>
LoadModule dims_module modules/libmod_dims.so
Expand All @@ -14,22 +17,55 @@
DimsDownloadTimeout 60000
DimsImagemagickTimeout 20000

# The largest source image to accept, in bytes. A larger source is refused
# before it is decoded. This bounds memory and a malicious SVG. 0 means no
# limit.
DimsMaxSourceBytes 33554432

# Refuse a fetch to a private or loopback address, so a request cannot reach an
# internal service. Set it to On only when your origin is on a private network.
# The cloud metadata address is refused whatever this holds.
DimsAllowPrivateAddresses off

# The hosts an image may come from. A leading *. matches subdomains only, not
# the apex. This list applies to /dims3/. A signed request skips it unless you
# enable DimsAllowlistSigned below.
DimsAddWhitelist images.example.com
DimsAddWhitelist *.cdn.example.com

# Apply the host allowlist to a signed request and to a redirect. A signed
# request skips the allowlist by default. Enable this once the allowlist covers
# your origins, so a signed request is held to the same list. Set it to log
# first to record what enforcing would refuse.
# DimsAllowlistSigned enforce

# /dims5/ is the endpoint to use for a new integration. It signs with
# HMAC-SHA256. Set a long random key and keep it out of version control.
DimsSigningKey CHANGE_ME_TO_A_LONG_RANDOM_KEY

# DimsAddClient <appId> <noImageUrl> <max-age> <downstream-ttl> <trust> <minSrc> <maxSrc> <secret>
#
# The secret is the key for /dims4/ signatures. Keep it out of version control.
DimsAddClient CLIENT http://example.com/no-image.png 604800 604800 trust 300 86400 changeme
# The secret is the key for /dims4/ signatures. Use a long random secret and
# keep it out of version control. Prefer /dims5/ for a new integration.
DimsAddClient CLIENT http://example.com/no-image.png 604800 604800 trust 300 86400 CHANGE_ME_TO_A_LONG_RANDOM_SECRET

# Decrypt the eurl parameter with AES-GCM, which is authenticated. The client
# that builds an eurl must encrypt with the same.
DimsEncryptionAlgorithm AES/GCM/NoPadding

# Shown when a source image cannot be fetched or processed.
DimsDefaultImageURL http://example.com/no-image.png

DimsCacheExpire 604800
DimsNoImageCacheExpire 60

# Hosts that /dims3/ may fetch from. A leading *. matches subdomains only, not
# the apex. Signed /dims4/ requests skip this list entirely, which
# specs/code-review.md records as finding C1.
DimsAddWhitelist images.example.com
DimsAddWhitelist *.cdn.example.com
# The status page reports counters. Off drops the component versions, which name
# the libraries a caller would target.
DimsStatusVerbose off

<Location /dims5/>
SetHandler dims5
</Location>

<Location /dims3/>
SetHandler dims3
Expand All @@ -39,6 +75,10 @@ DimsAddWhitelist *.cdn.example.com
SetHandler dims4
</Location>

# Restrict the status page to your monitoring host. Uncomment a Require line
# once mod_authz_host is loaded.
<Location /dims-status/>
SetHandler dims-status
# Require local
# Require ip 10.0.0.0/8
</Location>
5 changes: 5 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ ENV DIMS_SIGNING_KEY="-"
ENV DIMS_WHITELIST=""
ENV DIMS_CACHE_EXPIRE=604800
ENV DIMS_NO_IMAGE_CACHE_EXPIRE=60
ENV DIMS_MAX_SOURCE_BYTES=33554432
ENV DIMS_ALLOWLIST_SIGNED=log
ENV DIMS_ALLOW_PRIVATE_ADDRESSES=off
ENV DIMS_STATUS_VERBOSE=off
ENV DIMS_ENCRYPTION_ALGORITHM=AES/GCM/NoPadding
ENV LC_ALL="C"

# The image libraries are not installed here. The builder compiles each one
Expand Down
29 changes: 26 additions & 3 deletions docker/dims.conf
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ CustomLog /proc/self/fd/1 common
DimsDownloadTimeout ${DIMS_DOWNLOAD_TIMEOUT}
DimsImagemagickTimeout ${DIMS_IMAGEMAGICK_TIMEOUT}

# The largest source image to accept, in bytes. A larger source is refused
# before it is decoded. 0 means no limit.
DimsMaxSourceBytes ${DIMS_MAX_SOURCE_BYTES}

# Apply the host allowlist to a signed request and to a redirect. The default
# is log, so a signed request reaches any host. Set DIMS_ALLOWLIST_SIGNED to
# enforce to hold a signed request to the allowlist, once DIMS_WHITELIST covers
# your origins.
DimsAllowlistSigned ${DIMS_ALLOWLIST_SIGNED}

# Refuse a fetch to a private or loopback address. The default is off. Set
# DIMS_ALLOW_PRIVATE_ADDRESSES to On when the origin is on a private network.
DimsAllowPrivateAddresses ${DIMS_ALLOW_PRIVATE_ADDRESSES}

# The status page reports counters. The default drops the component versions.
DimsStatusVerbose ${DIMS_STATUS_VERBOSE}

# DimsClient <appId> [<noImageUrl> <cache control max-age> <edge control downstream-ttl> <trustSource?> <minSourceCache> <maxSourceCache> <password>]
DimsAddClient ${DIMS_CLIENT} ${DIMS_NO_IMAGE_URL} ${DIMS_CACHE_CONTROL_MAX_AGE} ${DIMS_EDGE_CONTROL_DOWNSTREAM_TTL} ${DIMS_TRUST_SOURCE} ${DIMS_MIN_SOURCE_CACHE} ${DIMS_MAX_SOURCE_CACHE} ${DIMS_SECRET}

Expand All @@ -19,11 +36,17 @@ DimsCacheExpire ${DIMS_CACHE_EXPIRE}
DimsNoImageCacheExpire ${DIMS_NO_IMAGE_CACHE_EXPIRE}
DimsUserAgentEnabled true

# Decrypt the eurl parameter with AES-GCM, which is authenticated. The default
# is AES/GCM/NoPadding. Set DIMS_ENCRYPTION_ALGORITHM to AES/ECB/PKCS5Padding
# only for a client that still encrypts with it.
DimsEncryptionAlgorithm ${DIMS_ENCRYPTION_ALGORITHM}

# A single dash means no key, and every /dims5/ request is then refused.
DimsSigningKey ${DIMS_SIGNING_KEY}

# The allowlist decides which hosts /dims3/ may fetch from. DIMS_WHITELIST
# defaults to empty in the Dockerfile, and an empty allowlist matches nothing.
# The allowlist decides which hosts a request may fetch from. DIMS_WHITELIST
# defaults to empty in the Dockerfile, and an empty allowlist matches nothing,
# so set it before the service can fetch a source.
DimsAddWhitelist ${DIMS_WHITELIST}

## Handler definitions. ##
Expand All @@ -42,4 +65,4 @@ DimsAddWhitelist ${DIMS_WHITELIST}

<Location /dims-status/>
SetHandler dims-status
</Location>
</Location>
23 changes: 23 additions & 0 deletions docs/docs/configuration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,29 @@ DimsSigningKey a-long-random-string
</Location>
```

## Hardening

The shipped `conf/mod_dims.conf.example` sets these. Each is safe to relax when
your deployment needs it.

- `DimsMaxSourceBytes` caps the source size, so one request cannot exhaust
memory. A malicious SVG is bounded by this too.
- `DimsAllowPrivateAddresses off` refuses a fetch to an internal address. The
cloud metadata address is refused whatever this holds.
- `DimsStatusVerbose off` drops the component versions from the status page, so
it does not name the libraries a caller would target. Restrict the
`/dims-status/` location to your monitoring host as well.
- `DimsEncryptionAlgorithm AES/GCM/NoPadding` decrypts the `eurl` parameter with
an authenticated cipher. The client that builds an `eurl` must encrypt with
the same.

Two more are worth enabling as your deployment allows.

- `DimsAllowlistSigned enforce` holds a signed request to the allowlist. A
signed request skips it by default. Enable it once the allowlist covers your
origins.
- Prefer `/dims5/` for a new integration. It signs with HMAC-SHA256.

## Every directive

| Directive | Default |
Expand Down
15 changes: 6 additions & 9 deletions docs/docs/endpoints/dims4.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,12 @@ expiry may be, which stops a caller minting a URL that never expires.

## Unsigned parameters

`overlay` and `optimizeResize` are only covered when `_keys` lists them. A
caller who omits `_keys` can change either and keep a valid signature.

:::warning
Six hexadecimal characters is 24 bits. One in 16.8 million random signatures
is accepted, so a caller sending requests at a moderate rate finds a working
signature for a URL of their choosing in hours.
`overlay` and `optimizeResize` are covered only when `_keys` lists them. List
them in `_keys` so the signature covers them.

:::tip
Set [`DimsAllowlistSigned`](/configuration/image-sources) to `enforce` so the
host allowlist applies to a signed request too. That bounds what a forged
signature can reach.
host allowlist applies to a signed request too. Prefer
[`/dims5/`](/endpoints/dims5), which signs with HMAC-SHA256, for a new
integration.
:::
3 changes: 1 addition & 2 deletions docs/docs/endpoints/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ Four handlers, each set on a location.
| [`dims-local`](/endpoints/local) | a file on disk | nothing |

`/dims5/` is the one to use. Its signature covers the commands, the image URL,
and every query parameter apart from five, and it compares a full HMAC-SHA256
rather than the first six characters of an MD5.
and every query parameter apart from five, and it uses HMAC-SHA256.

## Commands

Expand Down
Loading