refactor: extract signature package from imgcache #46

Merged
sneak merged 5 commits from refactor/extract-signature into main 2026-08-07 17:44:00 +02:00
6 changed files with 16 additions and 16 deletions
Showing only changes of commit 3dc1999543 - Show all commits

View File

@@ -29,7 +29,7 @@ Image-heavy web applications need a fast, caching reverse proxy that
can resize and transcode images on the fly. pixa fills that role as a
single, self-contained binary with no external runtime dependencies
beyond libvips. It supports HMAC-SHA256 signed URLs with expiration to
prevent abuse, and whitelisted source hosts for open access.
prevent abuse, and allowlisted source hosts for open access.
## Design
@@ -61,7 +61,7 @@ Images are only fetched from origins using TLS with valid certificates.
### Source Hosts
Source hosts may be whitelisted in the configuration. Non-whitelisted
Source hosts may be allowlisted in the configuration. Non-allowlisted
hosts require an HMAC-SHA256 signature.
#### Signature Specification
@@ -99,7 +99,7 @@ expiration 1704067200:
4. URL:
`/v1/image/cdn.example.com/photos/cat.jpg/800x600.webp?sig=<base64url>&exp=1704067200`
**Whitelist patterns:**
**Allowlist patterns:**
- **Exact match**: `cdn.example.com` — matches only that host
- **Suffix match**: `.example.com` — matches `cdn.example.com`,
@@ -110,7 +110,7 @@ expiration 1704067200:
Configured via YAML file (`--config`). Key settings:
- `access_control_allow_origin` — CORS origin
- `source_host_whitelist` — list of allowed upstream hosts
- `allowlist_hosts` — list of allowed upstream hosts
- `upstream_fetch_timeout` — timeout for origin requests
- `upstream_max_response_size` — max origin response size
- `downstream_timeout` — client response timeout

View File

@@ -9,13 +9,13 @@ maintenance_mode: false
state_dir: ./data
# Image proxy settings
# HMAC signing key for URL signatures (leave empty to require whitelist for all requests)
# HMAC signing key for URL signatures (leave empty to require allowlist for all requests)
# Generate with: openssl rand -base64 32
signing_key: "CHANGE_ME_generate_with_openssl_rand_base64_32"
# Hosts that don't require signatures
# Use "." prefix for wildcard subdomain matching (e.g., ".example.com" matches "cdn.example.com")
whitelist_hosts:
allowlist_hosts:
- s3.sneak.cloud
- static.sneak.cloud
- sneak.berlin

View File

@@ -41,7 +41,7 @@ type Config struct {
// Image proxy settings
SigningKey string // HMAC signing key for URL signatures
WhitelistHosts []string // Hosts that don't require signatures
AllowlistHosts []string // Hosts that don't require signatures
AllowHTTP bool // Allow non-TLS upstream (testing only)
UpstreamConnectionsPerHost int // Max concurrent connections per upstream host
}
@@ -69,7 +69,7 @@ func New(_ fx.Lifecycle, params Params) (*Config, error) {
MetricsUsername: getString(sc, "metrics.username", ""),
MetricsPassword: getString(sc, "metrics.password", ""),
SigningKey: getString(sc, "signing_key", ""),
WhitelistHosts: getStringSlice(sc, "whitelist_hosts"),
AllowlistHosts: getStringSlice(sc, "allowlist_hosts"),
AllowHTTP: getBool(sc, "allow_http", false),
UpstreamConnectionsPerHost: getInt(sc, "upstream_connections_per_host", DefaultUpstreamConnectionsPerHost),
}

View File

@@ -14,7 +14,7 @@ func TestGetStringSlice_YAMLList(t *testing.T) {
configPath := filepath.Join(tmpDir, "config.yml")
yamlContent := `
whitelist_hosts:
allowlist_hosts:
- static.sneak.cloud
- sneak.berlin
- s3.sneak.cloud
@@ -31,7 +31,7 @@ whitelist_hosts:
}
// Test that getStringSlice correctly parses YAML list
hosts := getStringSlice(sc, "whitelist_hosts")
hosts := getStringSlice(sc, "allowlist_hosts")
if len(hosts) != 3 {
t.Errorf("expected 3 hosts, got %d: %v", len(hosts), hosts)
@@ -54,7 +54,7 @@ func TestGetStringSlice_CommaSeparated(t *testing.T) {
tmpDir := t.TempDir()
configPath := filepath.Join(tmpDir, "config.yml")
yamlContent := `whitelist_hosts: "static.sneak.cloud, sneak.berlin, s3.sneak.cloud"`
yamlContent := `allowlist_hosts: "static.sneak.cloud, sneak.berlin, s3.sneak.cloud"`
err := os.WriteFile(configPath, []byte(yamlContent), 0644)
if err != nil {
@@ -66,7 +66,7 @@ func TestGetStringSlice_CommaSeparated(t *testing.T) {
t.Fatalf("failed to load config: %v", err)
}
hosts := getStringSlice(sc, "whitelist_hosts")
hosts := getStringSlice(sc, "allowlist_hosts")
if len(hosts) != 3 {
t.Errorf("expected 3 hosts, got %d: %v", len(hosts), hosts)
@@ -100,7 +100,7 @@ func TestGetStringSlice_Empty(t *testing.T) {
t.Fatalf("failed to load config: %v", err)
}
hosts := getStringSlice(sc, "whitelist_hosts")
hosts := getStringSlice(sc, "allowlist_hosts")
if hosts != nil && len(hosts) != 0 {
t.Errorf("expected nil or empty slice, got %v", hosts)

View File

@@ -84,7 +84,7 @@ func (s *Handlers) initImageService() error {
Cache: cache,
FetcherConfig: fetcherCfg,
SigningKey: s.config.SigningKey,
Allowlist: s.config.WhitelistHosts,
Allowlist: s.config.AllowlistHosts,
Logger: s.log,
})
if err != nil {

View File

@@ -97,8 +97,8 @@ else
fail "No encrypted URL to test"
fi
# Test 7: Fetch image via whitelisted host (direct proxy)
echo "--- Test 7: Fetch image via direct proxy (whitelisted host) ---"
# Test 7: Fetch image via allowlisted host (direct proxy)
echo "--- Test 7: Fetch image via direct proxy (allowlisted host) ---"
# URL format: /v1/image/<host>/<path>/<WxH>.<format>
PROXY_PATH="/v1/image/s3.sneak.cloud/sneak-public/2021/2021-04-18.untitled.a7r4.07723.jpg/400x300.jpeg"
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" "$BASE_URL$PROXY_PATH")