refactor: extract signature package from imgcache #46
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user