camo-rs

Crates.iocamo-rs
lib.rscamo-rs
version0.1.1
created_at2025-12-27 04:31:16.887097+00
updated_at2025-12-27 09:15:35.112264+00
descriptionCamo proxies insecure HTTP images over HTTPS, preventing mixed content warnings on secure pages.
homepage
repositoryhttps://github.com/AprilNEA/camo-rs
max_upload_size
id2006678
size128,552
AprilNEA (AprilNEA)

documentation

README

camo-rs

A high-performance SSL image proxy written in Rust. This is a Rust implementation of Camo, inspired by go-camo.

Camo proxies insecure HTTP images over HTTPS, preventing mixed content warnings on secure pages.

中文文档

Features

  • HMAC-SHA1 URL signing - Compatible with the original Camo
  • Dual URL format - Query string (/<digest>?url=...) and path-based (/<digest>/<encoded_url>)
  • Dual encoding - Supports both hex and base64 URL encoding
  • Content-Type filtering - Whitelist for image types, optional video/audio support
  • Size limits - Configurable maximum content length (default 5MB)
  • Redirect following - Configurable redirect limit (default 4)
  • SSRF protection - Blocks requests to private/internal networks (RFC1918)
  • Prometheus metrics - Optional /metrics endpoint
  • Structured logging - Built with tracing

Installation

As a library

Add to your Cargo.toml:

[dependencies]
camo = { git = "https://github.com/AprilNEA/camo-rs" }

From source

git clone https://github.com/AprilNEA/camo-rs.git
cd camo-rs
cargo build --release --features server

The binary will be at target/release/camo-rs.

Cargo Features

Feature Default Description
client Yes Core URL signing functionality with minimal dependencies
server No Full proxy server with CLI, metrics, and all dependencies
worker No Cloudflare Workers support
# Client only (minimal dependencies: hmac, sha1, hex, base64)
[dependencies]
camo = { git = "https://github.com/AprilNEA/camo-rs" }

# Server (includes tokio, axum, reqwest, etc.)
[dependencies]
camo = { git = "https://github.com/AprilNEA/camo-rs", features = ["server"] }

Cloudflare Workers

Deploy camo-rs to Cloudflare Workers for edge-based image proxying.

One-Click Deploy

Deploy to Cloudflare Workers

Important: After deployment, you must set your HMAC secret key:

wrangler secret put CAMO_KEY

Manual Deployment

Prerequisites

# Install wasm target
rustup target add wasm32-unknown-unknown

# Install wrangler CLI
npm install -g wrangler

Deploy

# Set your secret key
wrangler secret put CAMO_KEY

# Deploy
wrangler deploy

Configuration

Edit wrangler.toml:

name = "camo-rs"
main = "build/worker/shim.mjs"
compatibility_date = "2025-01-01"

[build]
command = "cargo install -q worker-build && worker-build --release --features worker"

[vars]
CAMO_MAX_SIZE = "5242880"  # 5MB

Environment Variables

Variable Description
CAMO_KEY HMAC secret key (use wrangler secret put)
CAMO_MAX_SIZE Maximum content size in bytes (default: 5MB)

Library Usage

use camo::{CamoUrl, Encoding};

// Create a CamoUrl generator with your secret key
let camo = CamoUrl::new("your-secret-key");

// Sign a URL
let signed = camo.sign("http://example.com/image.png");

// Get the full proxy URL
let url = signed.to_url("https://camo.example.com");
// => https://camo.example.com/abc123.../68747470...

// Or just the path
let path = signed.to_path();
// => /abc123.../68747470...

// Use base64 encoding instead of hex
let url = camo.sign("http://example.com/image.png")
    .base64()
    .to_url("https://camo.example.com");

// Set default encoding
let camo = CamoUrl::new("secret").with_encoding(Encoding::Base64);

// Convenience function
let url = camo::sign_url("secret", "http://example.com/image.png", "https://camo.example.com");

// Verify a digest
assert!(camo.verify("http://example.com/image.png", &signed.digest));

Usage

Start the server

# Using environment variable
CAMO_KEY=your-secret-key camo-rs

# Using CLI argument
camo-rs -k your-secret-key

# With custom options
camo-rs -k your-secret-key --listen 0.0.0.0:8081 --max-size 10485760

Generate signed URLs

# Generate URL components
camo-rs -k your-secret sign "https://example.com/image.png"
# Output:
# Digest: 54cec8e46f18f585268e3972432cd8da7aec6dc1
# Encoded URL: 68747470733a2f2f6578616d706c652e636f6d2f696d6167652e706e67
# Path: /54cec8e46f18f585268e3972432cd8da7aec6dc1/68747470...

# Generate full URL
camo-rs -k your-secret sign "https://example.com/image.png" --base "https://camo.example.com"
# Output: https://camo.example.com/54cec8e46f18f585268e3972432cd8da7aec6dc1/68747470...

# Use base64 encoding
camo-rs -k your-secret sign "https://example.com/image.png" --base64

URL Formats

The proxy accepts two URL formats:

# Path format
https://camo.example.com/<digest>/<hex-encoded-url>

# Query string format
https://camo.example.com/<digest>?url=<url-encoded-url>

Configuration

Option Environment Variable Default Description
-k, --key CAMO_KEY (required) HMAC key for URL signing
--listen CAMO_LISTEN 0.0.0.0:8080 Listen address
--max-size CAMO_LENGTH_LIMIT 5242880 Maximum content length in bytes
--max-redirects CAMO_MAX_REDIRECTS 4 Maximum redirects to follow
--timeout CAMO_SOCKET_TIMEOUT 10 Socket timeout in seconds
--allow-video CAMO_ALLOW_VIDEO false Allow video content types
--allow-audio CAMO_ALLOW_AUDIO false Allow audio content types
--block-private CAMO_BLOCK_PRIVATE true Block private networks (RFC1918)
--metrics CAMO_METRICS false Enable /metrics endpoint
--log-level CAMO_LOG_LEVEL info Log level (trace/debug/info/warn/error)

Integration

Generate URLs in your application

JavaScript/Node.js:

const crypto = require('crypto');

function camoUrl(key, url, baseUrl) {
  const digest = crypto.createHmac('sha1', key).update(url).digest('hex');
  const encodedUrl = Buffer.from(url).toString('hex');
  return `${baseUrl}/${digest}/${encodedUrl}`;
}

const url = camoUrl('your-secret', 'http://example.com/image.png', 'https://camo.example.com');

Python:

import hmac
import hashlib

def camo_url(key: str, url: str, base_url: str) -> str:
    digest = hmac.new(key.encode(), url.encode(), hashlib.sha1).hexdigest()
    encoded_url = url.encode().hex()
    return f"{base_url}/{digest}/{encoded_url}"

url = camo_url('your-secret', 'http://example.com/image.png', 'https://camo.example.com')

Rust:

use hmac::{Hmac, Mac};
use sha1::Sha1;

fn camo_url(key: &str, url: &str, base_url: &str) -> String {
    let mut mac = Hmac::<Sha1>::new_from_slice(key.as_bytes()).unwrap();
    mac.update(url.as_bytes());
    let digest = hex::encode(mac.finalize().into_bytes());
    let encoded_url = hex::encode(url.as_bytes());
    format!("{}/{}/{}", base_url, digest, encoded_url)
}

Endpoints

Path Description
/ Health check, returns "OK"
/health Health check, returns "OK"
/metrics Prometheus metrics (if enabled)
/<digest>/<encoded_url> Proxy endpoint (path format)
/<digest>?url=<url> Proxy endpoint (query format)

Docker

FROM rust:1.83-alpine AS builder
WORKDIR /app
COPY . .
RUN cargo build --release

FROM alpine:latest
COPY --from=builder /app/target/release/camo-rs /usr/local/bin/
EXPOSE 8080
ENTRYPOINT ["camo-rs"]
docker build -t camo-rs .
docker run -p 8080:8080 -e CAMO_KEY=your-secret camo-rs

License

MIT License

Credits

Commit count: 0

cargo fmt