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
32 changes: 32 additions & 0 deletions .github/workflows/tusd-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: tusd image
on:
pull_request:
paths: ["deploy/tusd/**", "docker-compose.yml", "deploy/self-hosted/compose.yml", ".github/workflows/tusd-image.yml"]
workflow_dispatch:
permissions:
contents: read
jobs:
image:
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
- name: Build and verify
run: |
docker build -t mave-tusd:test deploy/tusd
python3 deploy/tusd/smoke.py mave-tusd:test
- name: Publish tested image
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main'
env:
GH_TOKEN: ${{ github.token }}
run: |
printf '%s' "$GH_TOKEN" | docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin
image="ghcr.io/maveio/tusd:$GITHUB_SHA"
docker tag mave-tusd:test "$image"
docker push "$image"
docker logout ghcr.io
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ ARG BUILDER_IMAGE="docker.io/hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION
# runtime packages still affected in Debian. Build FFmpeg against this same base.
ARG RUNNER_IMAGE="docker.io/ubuntu:26.04@sha256:da6fc2be547864451aa253836dd926da33623312df4a9a243e35dc877c378a78"

FROM node:24.21.0-bookworm-slim@sha256:0e0ff40c39bc087845bfb27465a0df4ea419520094bc35842ff83dd8cbe6f9b6 AS node_build
FROM node:24.22.1-bookworm-slim@sha256:55fb5f5a527ea2c5c4a95f963e48ef90aa38c7b346054fdc8ee9957a2b55d205 AS node_build

FROM golang:1.27.3-alpine3.23 AS media_broker_build
WORKDIR /src
Expand Down
12 changes: 11 additions & 1 deletion deploy/self-hosted/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ services:
clickhouse:
image: clickhouse/clickhouse-server:25.8.33-alpine
environment:
SSL_CERT_FILE: /etc/mave-ca/ca-certificates.crt
SSL_CERT_DIR: /etc/mave-ca
CLICKHOUSE_USER: mave_core
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-}
CLICKHOUSE_DB: mave_metrics
Expand All @@ -93,6 +95,8 @@ services:
soft: 262144
hard: 262144
volumes:
- ../trust:/etc/mave-ca:ro
- ../trust/clickhouse.xml:/etc/clickhouse-server/config.d/public-trust.xml:ro
- clickhouse_data:/var/lib/clickhouse

minio:
Expand Down Expand Up @@ -154,7 +158,10 @@ services:
restart: unless-stopped

upload:
image: tusproject/tusd:v2.5.0
image: mave-tusd:local
build:
context: ../..
dockerfile: deploy/tusd/Dockerfile
command:
- -port=1080
- -behind-proxy=true
Expand Down Expand Up @@ -182,12 +189,15 @@ services:
caddy:
image: caddy:2.10.2-alpine
environment:
SSL_CERT_FILE: /etc/mave-ca/ca-certificates.crt
SSL_CERT_DIR: /etc/mave-ca
MAVE_CADDY_ADDRESS: ${MAVE_CADDY_ADDRESS:-:80}
ports:
- "${MAVE_HTTP_PORT:-4000}:80"
- "${MAVE_HTTPS_PORT:-4443}:443"
- "${MAVE_HTTPS_PORT:-4443}:443/udp"
volumes:
- ../trust:/etc/mave-ca:ro
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
Expand Down
11 changes: 11 additions & 0 deletions deploy/trust/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# External TLS trust

This public Mozilla CA bundle is generated from Alpine ca-certificates
20261020-r0 by `../tusd/Dockerfile`. It excludes the emSign ECC Root CA - G3
and Comodo AAA Services trust anchors used by compromised intermediate CAs.
There are no private certificates or keys in this file.

The Compose mounts deliberately set both SSL_CERT_FILE and SSL_CERT_DIR:
Go also loads certificate directories, so changing only the file can leave
removed roots reachable. ClickHouse uses an explicit OpenSSL client CA file.
Regenerate and test ordinary HTTPS and the incident test chains before updating.
3,137 changes: 3,137 additions & 0 deletions deploy/trust/ca-certificates.crt

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions deploy/trust/clickhouse.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<clickhouse>
<openSSL>
<client>
<caConfig>/etc/mave-ca/ca-certificates.crt</caConfig>
<loadDefaultCAFile>false</loadDefaultCAFile>
<verificationMode>strict</verificationMode>
</client>
</openSSL>
</clickhouse>
19 changes: 19 additions & 0 deletions deploy/tusd/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Preserve the upstream upload API while updating its TLS implementation.
FROM golang:1.27.3-alpine3.23@sha256:7fd63dd7c37f5ddaef15bd8fd3f3e4f90f0d50cd9cae255c7cf73887a494d8df AS build
ENV CGO_ENABLED=0 GOTOOLCHAIN=local
RUN go install -trimpath -ldflags="-s -w -X github.com/tus/tusd/v2/cmd/tusd/cli.VersionName=v2.5.0 -X github.com/tus/tusd/v2/cmd/tusd/cli.GitCommit=c5d5b0a0422db85e9aa41b0cfa6e34926d25d224" github.com/tus/tusd/v2/cmd/tusd@v2.5.0

FROM alpine:3.23@sha256:21f3e19bafab57a9ff7aa9dd571dac72f868370472c10f450cc0416cc1aca553
RUN apk add --no-cache ca-certificates=20261020-r0 \
&& sed -i -e '/^mozilla\/emSign_ECC_Root_CA_-_G3.crt$/s/^/!/' \
-e '/^mozilla\/Comodo_AAA_Services_root.crt$/s/^/!/' /etc/ca-certificates.conf \
&& update-ca-certificates --fresh \
&& addgroup -g 1000 -S tusd && adduser -u 1000 -S -G tusd tusd \
&& mkdir -p /srv/tusd-data /srv/tusd-hooks && chown -R tusd:tusd /srv/tusd-data /srv/tusd-hooks
COPY --from=build /go/bin/tusd /usr/local/bin/tusd
COPY --from=build /go/pkg/mod/github.com/tus/tusd/v2@v2.5.0/LICENSE.txt /licenses/tusd-LICENSE.txt
WORKDIR /srv/tusd-data
USER tusd
EXPOSE 8080
ENTRYPOINT ["/usr/local/bin/tusd"]
CMD ["--hooks-dir", "/srv/tusd-hooks"]
63 changes: 63 additions & 0 deletions deploy/tusd/smoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
"""Check the tusd image trust bundle and resumable-upload contract in isolation."""
import hashlib
import http.client
import json
from pathlib import Path
import ssl
import subprocess
import sys
import tempfile
import time

image = sys.argv[1]

def docker(*args):
return subprocess.check_output(["docker", *args], text=True).strip()

pem = docker("run", "--rm", "--network=none", "--entrypoint", "cat", image,
"/etc/ssl/certs/ca-certificates.crt")
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_verify_locations(cadata=pem)
blocked = {"86a1ecba089c4a8d3bbe2734c612ba341d813e043cf9e8a862cd5c57a36bbe6b",
"d7a7a0fb5d7e2731d771e9484ebcdef71d5f0c3e0a2948782bc83ee0ea699ef4",
"7dd78d5f4f13459a83dff9abbba62edbaf6f2d102bf257fd712f4d9f2746ed8d",
"f2edaaff3d9b701fb4d14c9f285f8c0bad90fc3bcc1fa07d47f4718edaba34a5"}
fingerprints = {hashlib.sha256(c).hexdigest() for c in context.get_ca_certs(binary_form=True)}
assert len(fingerprints) > 100 and not fingerprints.intersection(blocked)
assert "Version: v2.5.0" in docker("run", "--rm", image, "-version")
assert docker("run", "--rm", "--entrypoint", "id", image, "-u") == "1000"
container = docker("run", "-d", "--rm", "-p", "127.0.0.1::8080", image,
"-host=0.0.0.0", "-port=8080", "-upload-dir=/srv/tusd-data")
try:
port = int(json.loads(docker("inspect", container))[0]["NetworkSettings"]["Ports"]["8080/tcp"][0]["HostPort"])
def request(method, path, body=None, headers=None):
conn = http.client.HTTPConnection("127.0.0.1", port, timeout=5)
conn.request(method, path, body, headers or {})
response = conn.getresponse()
result = response.status, dict(response.getheaders()), response.read()
conn.close()
return result
for attempt in range(40):
try:
status, _, _ = request("OPTIONS", "/files/")
if status == 200: break
except (OSError, http.client.HTTPException): pass
time.sleep(0.25)
else: raise AssertionError("tusd did not become ready")
version = {"Tus-Resumable": "1.0.0"}
status, headers, _ = request("POST", "/files/", headers={**version, "Upload-Length": "6"})
assert status == 201, status
from urllib.parse import urlsplit
path = urlsplit(headers["Location"]).path
patch = {**version, "Content-Type": "application/offset+octet-stream"}
assert request("PATCH", path, b"abc", {**patch, "Upload-Offset": "0"})[0] == 204
status, headers, _ = request("HEAD", path, headers=version)
assert status == 200 and headers["Upload-Offset"] == "3"
assert request("PATCH", path, b"bad", {**patch, "Upload-Offset": "0"})[0] == 409
assert request("PATCH", path, b"def", {**patch, "Upload-Offset": "3"})[0] == 204
status, headers, _ = request("HEAD", path, headers=version)
assert status == 200 and headers["Upload-Offset"] == "6"
print("PASS: hardened roots, unprivileged user, create/PATCH/resume/conflict/completion")
finally:
subprocess.run(["docker", "rm", "-f", container], check=False, stdout=subprocess.DEVNULL)
9 changes: 8 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,17 @@ services:
clickhouse:
image: clickhouse/clickhouse-server
environment:
- SSL_CERT_FILE=/etc/mave-ca/ca-certificates.crt
- SSL_CERT_DIR=/etc/mave-ca
- CLICKHOUSE_USER=default
- CLICKHOUSE_PASSWORD=password
- CLICKHOUSE_DB=mave_metrics
ports:
- "127.0.0.1:8123:8123"
- "127.0.0.1:9002:9000"
volumes:
- ./deploy/trust:/etc/mave-ca:ro
- ./deploy/trust/clickhouse.xml:/etc/clickhouse-server/config.d/public-trust.xml:ro
- clickhouse_data:/var/lib/clickhouse
ulimits:
nofile:
Expand Down Expand Up @@ -88,7 +92,10 @@ services:
condition: service_started

upload:
image: tusproject/tusd:v2.5.0
image: mave-tusd:local
build:
context: .
dockerfile: deploy/tusd/Dockerfile
command:
- "-port=1080"
- "-behind-proxy=true"
Expand Down
Loading