From 5f035599b36168c18a491a710fceeb55d658f596 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Fri, 2 Jan 2026 16:20:13 -0800 Subject: [PATCH 1/3] Updated documentation --- .env.example | 70 +++++++ .tool-versions | 4 +- README.md | 262 +++----------------------- docs/.prettierignore | 3 + docs/.prettierrc | 7 + docs/.vitepress/config.mts | 107 ++++++++--- docs/README.md | 17 +- docs/guide/ad-serving.md | 14 +- docs/guide/api-reference.md | 93 +++++++-- docs/guide/architecture.md | 4 + docs/guide/configuration-reference.md | 219 ++++++++++++++------- docs/guide/configuration.md | 58 ++++-- docs/guide/creative-processing.md | 226 ++++++++++++++++------ docs/guide/environment-variables.md | 31 +++ docs/guide/error-reference.md | 83 +++++++- docs/guide/fastly.md | 101 ++++++++++ docs/guide/first-party-proxy.md | 89 +++++++-- docs/guide/gdpr-compliance.md | 8 +- docs/guide/getting-started.md | 93 +++++++-- docs/guide/integration-guide.md | 14 +- docs/guide/integrations-overview.md | 43 +++-- docs/guide/key-rotation.md | 34 +++- docs/guide/request-signing.md | 33 +++- docs/guide/rsc-hydration.md | 14 +- docs/guide/synthetic-ids.md | 4 +- docs/guide/testing.md | 6 +- docs/guide/what-is-trusted-server.md | 4 + docs/index.md | 6 +- docs/package-lock.json | 41 ++++ docs/package.json | 6 +- 30 files changed, 1181 insertions(+), 513 deletions(-) create mode 100644 .env.example create mode 100644 docs/.prettierignore create mode 100644 docs/.prettierrc create mode 100644 docs/guide/fastly.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e18c986 --- /dev/null +++ b/.env.example @@ -0,0 +1,70 @@ +# Trusted Server Environment Variables +# Copy this file to .env.dev, .env.staging, or .env.production and fill in values +# See docs/guide/configuration.md for details + +# ============================================================================= +# Publisher Settings +# ============================================================================= +TRUSTED_SERVER__PUBLISHER__DOMAIN=publisher.com +TRUSTED_SERVER__PUBLISHER__COOKIE_DOMAIN=.publisher.com +TRUSTED_SERVER__PUBLISHER__ORIGIN_URL=https://origin.publisher.com +TRUSTED_SERVER__PUBLISHER__PROXY_SECRET= + +# ============================================================================= +# Synthetic ID Settings +# ============================================================================= +TRUSTED_SERVER__SYNTHETIC__COUNTER_STORE=counter_store +TRUSTED_SERVER__SYNTHETIC__OPID_STORE=opid_store +TRUSTED_SERVER__SYNTHETIC__SECRET_KEY= +# Template variables: client_ip, user_agent, first_party_id, auth_user_id, publisher_domain, accept_language +TRUSTED_SERVER__SYNTHETIC__TEMPLATE={{ client_ip }}:{{ user_agent }}:{{ first_party_id }} + +# ============================================================================= +# Request Signing (optional) +# ============================================================================= +TRUSTED_SERVER__REQUEST_SIGNING__ENABLED=false +# TRUSTED_SERVER__REQUEST_SIGNING__CONFIG_STORE_ID= +# TRUSTED_SERVER__REQUEST_SIGNING__SECRET_STORE_ID= + +# ============================================================================= +# Response Headers (optional) +# ============================================================================= +# TRUSTED_SERVER__RESPONSE_HEADERS__X_CUSTOM_HEADER=custom-value + +# ============================================================================= +# Integrations +# ============================================================================= + +# Prebid +TRUSTED_SERVER__INTEGRATIONS__PREBID__ENABLED=false +# TRUSTED_SERVER__INTEGRATIONS__PREBID__SERVER_URL=https://prebid-server.com/openrtb2/auction +# TRUSTED_SERVER__INTEGRATIONS__PREBID__TIMEOUT_MS=1000 +# TRUSTED_SERVER__INTEGRATIONS__PREBID__BIDDERS=kargo,rubicon,appnexus +# TRUSTED_SERVER__INTEGRATIONS__PREBID__AUTO_CONFIGURE=false +# TRUSTED_SERVER__INTEGRATIONS__PREBID__DEBUG=false + +# Next.js +TRUSTED_SERVER__INTEGRATIONS__NEXTJS__ENABLED=false +# TRUSTED_SERVER__INTEGRATIONS__NEXTJS__REWRITE_ATTRIBUTES=href,link,url + +# Didomi +TRUSTED_SERVER__INTEGRATIONS__DIDOMI__ENABLED=false +# TRUSTED_SERVER__INTEGRATIONS__DIDOMI__SDK_ORIGIN=https://sdk.privacy-center.org +# TRUSTED_SERVER__INTEGRATIONS__DIDOMI__API_ORIGIN=https://api.privacy-center.org + +# Permutive +TRUSTED_SERVER__INTEGRATIONS__PERMUTIVE__ENABLED=false +# TRUSTED_SERVER__INTEGRATIONS__PERMUTIVE__ORGANIZATION_ID= +# TRUSTED_SERVER__INTEGRATIONS__PERMUTIVE__WORKSPACE_ID= +# TRUSTED_SERVER__INTEGRATIONS__PERMUTIVE__PROJECT_ID= + +# Lockr +TRUSTED_SERVER__INTEGRATIONS__LOCKR__ENABLED=false +# TRUSTED_SERVER__INTEGRATIONS__LOCKR__APP_ID= +# TRUSTED_SERVER__INTEGRATIONS__LOCKR__CACHE_TTL_SECONDS=3600 + +# ============================================================================= +# Rewrite Configuration (optional) +# ============================================================================= +# Comma-separated list of domains to exclude from first-party rewriting +# TRUSTED_SERVER__REWRITE__EXCLUDE_DOMAINS=*.edgecompute.app diff --git a/.tool-versions b/.tool-versions index b0f6b2d..0828da5 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,3 @@ -fastly 13.1.0 +fastly 13.3.0 rust 1.91.1 -nodejs 24.10.0 +nodejs 24.12.0 diff --git a/README.md b/README.md index 49f1dc8..140ca93 100644 --- a/README.md +++ b/README.md @@ -1,250 +1,32 @@ # Trusted Server -:information_source: Trusted Server is an open-source, cloud based orchestration framework and runtime for publishers. It moves code execution and operations that traditionally occurs in browsers (via 3rd party JS) to secure, zero-cold-start [WASM](https://webassembly.org) binaries running in [WASI](https://github.com/WebAssembly/WASI) supported environments. It importantly gives publishers benefits such as: dramatically increasing control over how and who they share their data with (while maintaining user-privacy compliance), increasing revenue from inventory inside cookie restricted or non-JS environments, ability to serve all assets under 1st party context, and provides secure cryptographic functions to ensure trust across the programmatic ad ecosystem. +Trusted Server is an open-source, cloud-based orchestration framework and runtime for publishers. It moves code execution and operations that traditionally occur in browsers (via 3rd party JS) to secure, zero-cold-start [WASM](https://webassembly.org) binaries running in [WASI](https://github.com/WebAssembly/WASI) supported environments. -Trusted Server is the new execution layer for the open-web, returning control of 1st party data, security, and overall user-experience back to publishers. +**Key benefits:** -At this time, Trusted Server is designed to work with Fastly Compute. Follow these steps to configure Fastly Compute and deploy it. +- Dramatically increases control over data sharing while maintaining privacy compliance +- Increases revenue from inventory in cookie-restricted or non-JS environments +- Serves all assets under first-party context +- Provides secure cryptographic functions for trust across the programmatic ad ecosystem -## Getting Started: Edge-Cloud Support on Fastly +At this time, Trusted Server is designed to work with [Fastly Compute](https://www.fastly.com/products/compute). -- Create account at Fastly if you don’t have one - manage.fastly.com -- Log in to the Fastly control panel. - - Go to Account > API tokens > Personal tokens. - - Click Create token - - Name the Token - - Choose User Token - - Choose Global API Access - - Choose what makes sense for your Org in terms of Service Access - - Copy key to a secure location because you will not be able to see it again +## Documentation -- Create new Compute Service - - Click Compute and Create Service - - Click “Create Empty Service” (below main options) - - Add your domain of the website you’ll be testing or using and click update - - Click on “Origins” section and add your ad-server / SSP integration information as hostnames (note after you save this information you can select port numbers and TLS on/off) - - IMPORTANT: when you enter the FQDN or IP ADDR information and click Add you need to enter a “Name” in the first field that will be referenced in your code so something like “my_ad_integration_1” - - +📚 **[View Full Documentation](docs/guide/getting-started.md)** -:warning: With a dev account, Fastly gives you a test domain by default, but you’re also able to create a CNAME to your own domain when you’re ready, along with 2 free TLS certs (non-wildcard). Note that Fastly Compute ONLY accepts client traffic via TLS, though origins and backends can be non-TLS. +| Guide | Description | +| ---------------------------------------------------- | -------------------------------------------- | +| [Getting Started](docs/guide/getting-started.md) | Installation, setup, and first deployment | +| [Fastly Setup](docs/guide/fastly.md) | Fastly account, Compute service, and origins | +| [Configuration](docs/guide/configuration.md) | Configuration options and settings | +| [Synthetic IDs](docs/guide/synthetic-ids.md) | Privacy-preserving identifier generation | +| [Ad Serving](docs/guide/ad-serving.md) | Ad server integration and setup | +| [First-Party Proxy](docs/guide/first-party-proxy.md) | Proxy configuration for first-party context | +| [Request Signing](docs/guide/request-signing.md) | Cryptographic request signing with Ed25519 | +| [API Reference](docs/guide/api-reference.md) | Complete API endpoint documentation | +| [Integration Guide](docs/guide/integration-guide.md) | Building custom integrations | -## CLI and OS Tools Installation +## License -### Brew - -:warning: Follow the prompts before and afterwards (to configure system path, etc) - -#### Install Brew - -```sh -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -``` - -### Fastly CLI - -#### Install Fastly CLI - -```sh -brew install fastly/tap/fastly -``` - -#### Verify Installation and Version - -```sh -fastly version -``` - -:warning: fastly cli version should be at least v12.1.0 - -#### Create profile and follow interactive prompt for pasting your API Token created earlier: - -```sh -fastly profile create -``` - -### Rust - -#### Install Rust with asdf (our preference) - -```sh -brew install asdf -asdf plugin add rust -asdf install rust $(grep '^rust ' .tool-versions | awk '{print $2}') -asdf reshim -``` - -### NodeJS - -#### Install NodeJS with asdf - -```sh -brew install asdf -asdf plugin add nodejs -asdf install nodejs $(grep '^nodejs ' .tool-versions | awk '{print $2}') -asdf reshim -``` - -#### Fix path for Bash - -Edit ~/.bash_profile to add path for asdf shims: - -```sh -export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH" -``` - -#### Fix path for ZSH - -Edit ~/.zshrc to add path for asdf shims: - -```sh -export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH" -``` - -#### Other shells - -See https://asdf-vm.com/guide/getting-started.html#_2-configure-asdf - -### Clone Trusted Server and Configure Build - -#### Clone Project (assumes you have 'git' installed on your system) - -```sh -git clone git@github.com:IABTechLab/trusted-server.git -``` - -### Configure - -#### Edit configuration files - -:information_source: Note that you'll have to edit the following files for your setup: - -- fastly.toml (service ID, author, description, Config/Secret Store IDs for request signing) -- trusted-server.toml (KV store ID names - optional, request signing configuration) - -### Build - -```sh -cargo build -``` - -### Deploy to Fastly - -```sh -fastly compute publish -``` - -## Devleopment - -#### Install viceroy for running tests - -```sh -cargo install viceroy -``` - -#### Run Fastly server locally - -- Review configuration for [local_server](fastly.toml#L16) -- Review env variables overrides in [.env.dev](.env.dev) - -```sh -export $(grep -v '^#' .env.dev | xargs -0) -``` - -```sh -fastly -i compute serve -``` - -#### Tests - -```sh -cargo test -``` - -:warning: if test fails `viceroy` will not display line number of the failed test. Rerun it with `cargo test_details`. - -#### Additional Rust Commands - -- `cargo fmt`: Ensure uniform code formatting -- `cargo clippy`: Ensure idiomatic code -- `cargo check`: Ensure compilation succeeds on Linux, MacOS, Windows and WebAssembly -- `cargo bench`: Run all benchmarks - -## Request Signing - -Trusted Server supports cryptographic signing of OpenRTB requests and other API calls using Ed25519 keys. - -### Configuration - -Request signing requires Fastly Config Store and Secret Store for key management: - -1. **Create Fastly Stores** (via Fastly Control Panel or CLI): - - Config Store: `jwks_store` - stores public keys (JWKs) and key metadata - - Secret Store: `signing_keys` - stores private signing keys - -2. **Configure in trusted-server.toml**: - -```toml -[request_signing] -enabled = true # Set to true to enable request signing -config_store_id = "" # Config Store ID from Fastly -secret_store_id = "" # Secret Store ID from Fastly -``` - -### Key Management Endpoints - -Once configured, the following endpoints are available: - -- **`GET /.well-known/ts.jwks.json`**: Returns active public keys in JWKS format for signature verification -- **`POST /verify-signature`**: Verifies a signature against a payload and key ID (useful for testing) - - Request body: `{"payload": "...", "signature": "...", "kid": "..."}` - - Response: `{"verified": true/false, "kid": "...", "message": "..."}` - -#### Admin Endpoints (Key Rotation) - -- **`POST /admin/keys/rotate`**: Generates and activates a new signing key - - Optional body: `{"kid": "custom-key-id"}` (auto-generates date-based ID if omitted) - - Response includes new key ID, previous key ID, and active keys list -- **`POST /admin/keys/deactivate`**: Deactivates or deletes a key - - Request body: `{"kid": "key-to-deactivate", "delete": false}` - - Set `delete: true` to permanently remove the key (also deactivates it) - -:warning: Key rotation keeps both the new and previous key active to allow for graceful transitions. Deactivate old keys manually when no longer needed. - -## First-Party Endpoints - -- `/first-party/ad` (GET): returns HTML for a single slot (`slot`, `w`, `h` query params). The server inspects returned creative HTML and rewrites: -- All absolute images and iframes to `/first-party/proxy?tsurl=&&tstoken=` (1×1 pixels are detected server‑side heuristically for logging). The `tstoken` is derived from encrypting the full target URL and hashing it. -- `/third-party/ad` (POST): accepts tsjs ad units and proxies to Prebid Server. -- `/first-party/proxy` (GET): unified proxy for resources referenced by creatives. - - Query params: - - `tsurl`: Target URL without query (base URL) — required - - Any original target query parameters are included at top level as-is (order preserved) - - `tstoken`: Base64 URL‑safe (no padding) SHA‑256 digest of the encrypted full target URL — required - - Behavior: - - Reconstructs the full target URL from `tsurl` + provided parameters in order, computes `tstoken` by encrypting with XChaCha20‑Poly1305 (deterministic nonce) and hashing the bytes with SHA‑256, and validates it. - - HTML responses: proxied and rewritten (images/iframes/pixels) via creative rewriter - - Image responses: proxied; if content‑type is missing, sets `image/*`; logs likely 1×1 pixels via size/URL heuristics - - Follows HTTP redirects (301/302/303/307/308) up to four hops, reapplying the forwarded synthetic ID and switching to `GET` after a 303; logs when the redirect limit is reached. - - When forwarding to the target URL, no `tstoken` is included (it is not part of the target URL). -- Synthetic ID propagation: reads the trusted ID from the incoming cookie/header and appends `synthetic_id=` to the target URL sent to the third-party origin while preserving existing query strings. - - Redirect following re-applies the identifier on each hop so downstream origins see a consistent ID even when assets bounce through intermediate trackers. - -- `/first-party/click` (GET): first‑party click redirect handler for anchors and clickable areas. - - Query params: same as `/first-party/proxy` (uses `tsurl`, original params, `tstoken`). - - Behavior: - - Validates `tstoken` against the reconstructed full URL (same enc+SHA256 scheme). - - Emits a `302 Found` with `Location: ` — content is not parsed or proxied. - - If a synthetic identifier is available, appends `synthetic_id=` to the redirect target. - - Logs click metadata (tsurl, whether params are present, target URL, referer, user agent, and Trusted Server ID header) for observability. - -- Publisher origin proxy (`handle_publisher_request`): retrieves/generates the synthetic ID, stamps the response with `X-Synthetic-*` headers, and sets the `synthetic_id` cookie (Secure, SameSite=Lax) when absent so subsequent creative and click proxies can propagate the identifier. - -Notes - -- Rewriting uses `lol_html`. Only absolute and protocol‑relative URLs are rewritten; relative URLs are left unchanged. -- For the proxy endpoint, the base URL is carried in `tsurl`, the original query parameters are preserved individually, and `tstoken` authenticates the reconstructed full URL. -- Synthetic identifiers are generated by `crates/common/src/synthetic.rs` and are surfaced in three places: publisher responses (headers + cookie), creative proxy target URLs (`synthetic_id` query param), and click redirect URLs. This ensures downstream integrations can correlate impressions and clicks without direct third-party cookies. - -## Integration Modules - -- See [`docs/integration_guide.md`](docs/integration_guide.md) for the full integration module guide, covering configuration, proxy routing, HTML shim hooks, and the `testlight` example implementation. +Apache License 2.0 diff --git a/docs/.prettierignore b/docs/.prettierignore new file mode 100644 index 0000000..94aa6e0 --- /dev/null +++ b/docs/.prettierignore @@ -0,0 +1,3 @@ +.vitepress/cache +.vitepress/dist +node_modules diff --git a/docs/.prettierrc b/docs/.prettierrc new file mode 100644 index 0000000..364896f --- /dev/null +++ b/docs/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": false, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "proseWrap": "preserve" +} diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 36f8188..27923bc 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -1,10 +1,58 @@ import { defineConfig } from 'vitepress' +import { readFileSync } from 'node:fs' +import { resolve, dirname } from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +// Parse .tool-versions file to extract version numbers +function getToolVersions(): Record { + const toolVersionsPath = resolve(__dirname, '../../.tool-versions') + const versions: Record = {} + + try { + const content = readFileSync(toolVersionsPath, 'utf-8') + for (const line of content.split('\n')) { + const trimmed = line.trim() + if (trimmed && !trimmed.startsWith('#')) { + const [tool, version] = trimmed.split(/\s+/) + if (tool && version) { + versions[tool] = version + } + } + } + } catch (e) { + console.warn('Could not read .tool-versions file:', e) + } + + return versions +} + +const toolVersions = getToolVersions() // https://vitepress.dev/reference/site-config export default defineConfig({ - title: "Trusted Server", - description: "Privacy-preserving edge computing for ad serving and synthetic ID generation", - base: "/trusted-server", + title: 'Trusted Server', + description: + 'Privacy-preserving edge computing for ad serving and synthetic ID generation', + base: '/trusted-server', + ignoreDeadLinks: true, + + // Replace version placeholders like {{NODEJS_VERSION}} with values from .tool-versions + markdown: { + config: (md) => { + const originalParse = md.parse.bind(md) + md.parse = (src: string, env: Record) => { + let processed = src + for (const [tool, version] of Object.entries(toolVersions)) { + const placeholder = `{{${tool.toUpperCase()}_VERSION}}` + processed = processed.replaceAll(placeholder, version) + } + return originalParse(processed, env) + } + }, + }, + themeConfig: { // https://vitepress.dev/reference/default-theme-config nav: [ @@ -16,9 +64,13 @@ export default defineConfig({ { text: 'Introduction', items: [ - { text: 'What is Trusted Server?', link: '/guide/what-is-trusted-server' }, - { text: 'Getting Started', link: '/guide/getting-started' } - ] + { + text: 'What is Trusted Server?', + link: '/guide/what-is-trusted-server', + }, + { text: 'Getting Started', link: '/guide/getting-started' }, + { text: 'Fastly Setup', link: '/guide/fastly' }, + ], }, { text: 'Core Concepts', @@ -28,49 +80,56 @@ export default defineConfig({ { text: 'Ad Serving', link: '/guide/ad-serving' }, { text: 'First-Party Proxy', link: '/guide/first-party-proxy' }, { text: 'Creative Processing', link: '/guide/creative-processing' }, - { text: 'Integrations Overview', link: '/guide/integrations-overview' } - ] + { + text: 'Integrations Overview', + link: '/guide/integrations-overview', + }, + ], }, { text: 'Security', items: [ { text: 'Request Signing', link: '/guide/request-signing' }, - { text: 'Key Rotation', link: '/guide/key-rotation' } - ] + { text: 'Key Rotation', link: '/guide/key-rotation' }, + ], }, { text: 'Development', items: [ { text: 'Architecture', link: '/guide/architecture' }, { text: 'Configuration', link: '/guide/configuration' }, - { text: 'Configuration Reference', link: '/guide/configuration-reference' }, + { + text: 'Configuration Reference', + link: '/guide/configuration-reference', + }, { text: 'Testing', link: '/guide/testing' }, - { text: 'Integration Guide', link: '/guide/integration-guide' } - ] + { text: 'Integration Guide', link: '/guide/integration-guide' }, + ], }, { text: 'Advanced', - items: [ - { text: 'RSC Hydration', link: '/guide/rsc-hydration' } - ] + items: [{ text: 'RSC Hydration', link: '/guide/rsc-hydration' }], }, { text: 'Reference', items: [ { text: 'API Reference', link: '/guide/api-reference' }, - { text: 'Environment Variables', link: '/guide/environment-variables' }, - { text: 'Error Reference', link: '/guide/error-reference' } - ] - } + { + text: 'Environment Variables', + link: '/guide/environment-variables', + }, + { text: 'Error Reference', link: '/guide/error-reference' }, + ], + }, ], socialLinks: [ - { icon: 'github', link: 'https://github.com/IABTechLab/trusted-server' } + { icon: 'github', link: 'https://github.com/IABTechLab/trusted-server' }, ], footer: { message: 'Released under the Apache License 2.0.', - copyright: 'Copyright © 2018-present IAB Technology Laboratory' - } - } + copyright: 'Copyright © 2018-present IAB Technology Laboratory', + }, + }, }) diff --git a/docs/README.md b/docs/README.md index b25785a..0be0eb3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,7 +6,7 @@ VitePress documentation site for Trusted Server. ### Prerequisites -- Node.js 24.10.0 (or version specified in `.tool-versions`) +- Node.js (version specified in `.tool-versions`) - npm ### Setup @@ -42,13 +42,15 @@ The documentation is automatically deployed to GitHub Pages when changes are pus ### Custom Domain Setup 1. **Update CNAME file**: Edit `docs/public/CNAME` with your domain: + ``` docs.yourdomain.com ``` 2. **Configure DNS**: Add DNS records at your domain provider: - + **Option A - CNAME Record** (recommended for subdomains): + ``` Type: CNAME Name: docs @@ -56,6 +58,7 @@ The documentation is automatically deployed to GitHub Pages when changes are pus ``` **Option B - A Records** (for apex domains): + ``` Type: A Name: @ @@ -73,11 +76,13 @@ The documentation is automatically deployed to GitHub Pages when changes are pus ### Workflow Details -**Trigger**: +**Trigger**: + - Push to `main` branch (only when `docs/**` changes) - Manual trigger via Actions tab **Build Process**: + 1. Checkout repository with full history (for `lastUpdated` feature) 2. Setup Node.js (version from `.tool-versions`) 3. Install dependencies (`npm ci`) @@ -86,6 +91,7 @@ The documentation is automatically deployed to GitHub Pages when changes are pus 6. Deploy to GitHub Pages **Permissions Required**: + - `contents: read` - Read repository - `pages: write` - Deploy to Pages - `id-token: write` - OIDC token for deployment @@ -95,11 +101,13 @@ The documentation is automatically deployed to GitHub Pages when changes are pus ### Build Fails in GitHub Actions **Check**: + - Node.js version matches `.tool-versions` - All dependencies in `package.json` are correct - Build succeeds locally (`npm run build`) **View Logs**: + 1. Go to **Actions** tab in GitHub 2. Click on failed workflow run 3. Review build logs @@ -107,12 +115,14 @@ The documentation is automatically deployed to GitHub Pages when changes are pus ### Custom Domain Not Working **Check**: + - DNS records propagated (use `dig docs.yourdomain.com`) - CNAME file exists in `docs/public/CNAME` - Custom domain verified in GitHub Pages settings - HTTPS enforced (may take up to 24 hours) **DNS Verification**: + ```bash # Check CNAME record dig docs.yourdomain.com CNAME @@ -124,6 +134,7 @@ dig yourdomain.com A ### 404 Errors **Check**: + - VitePress `base` config (should not be set for custom domains) - Links use correct paths (start with `/`) - Build output in `docs/.vitepress/dist` is correct diff --git a/docs/guide/ad-serving.md b/docs/guide/ad-serving.md index 6a05ed6..2999e12 100644 --- a/docs/guide/ad-serving.md +++ b/docs/guide/ad-serving.md @@ -11,6 +11,7 @@ Trusted Server provides edge-based ad serving with built-in GDPR compliance and ### Equativ Primary ad server integration with support for: + - Direct ad requests - Creative proxying - Click tracking @@ -19,6 +20,7 @@ Primary ad server integration with support for: ### Prebid Real-time bidding integration: + - Header bidding support - Bid caching - Timeout management @@ -53,6 +55,7 @@ cache_ttl = 300 ### Proxy Mode Creatives can be proxied through Trusted Server for: + - Security scanning - Content modification - Click tracking injection @@ -61,6 +64,7 @@ Creatives can be proxied through Trusted Server for: ### Direct Mode Creatives served directly from ad server: + - Lower latency - Reduced edge load - Less control over content @@ -72,15 +76,16 @@ Creatives served directly from ad server: ```javascript // Placeholder example trustedServer.trackImpression({ - adId: 'ad-123', - syntheticId: 'synthetic-xyz', - consent: true -}); + adId: 'ad-123', + syntheticId: 'synthetic-xyz', + consent: true, +}) ``` ### Click Tracking Click tracking with privacy preservation: + - No PII in URLs - Synthetic ID only (with consent) - Encrypted parameters @@ -97,6 +102,7 @@ Click tracking with privacy preservation: ### Timeouts Configurable timeouts for: + - Ad server requests - Prebid auctions - Creative fetching diff --git a/docs/guide/api-reference.md b/docs/guide/api-reference.md index b10d345..4131ec3 100644 --- a/docs/guide/api-reference.md +++ b/docs/guide/api-reference.md @@ -25,19 +25,23 @@ Server-side ad rendering endpoint. Returns complete HTML for a single ad slot. | `h` | integer | Yes | Ad height in pixels | **Response:** + - **Content-Type:** `text/html; charset=utf-8` - **Body:** Complete HTML creative with first-party proxying applied **Example:** + ```bash curl "https://edge.example.com/first-party/ad?slot=header-banner&w=728&h=90" ``` **Response Headers:** + - `X-Synthetic-Trusted-Server` - Stable synthetic ID - `X-Synthetic-Fresh` - One-time fresh ID **Use Cases:** + - Server-side ad rendering - Direct iframe embedding - First-party ad delivery @@ -49,6 +53,7 @@ curl "https://edge.example.com/first-party/ad?slot=header-banner&w=728&h=90" Client-side auction endpoint for TSJS library. **Request Body:** + ```json { "adUnits": [ @@ -56,7 +61,10 @@ Client-side auction endpoint for TSJS library. "code": "header-banner", "mediaTypes": { "banner": { - "sizes": [[728, 90], [970, 250]] + "sizes": [ + [728, 90], + [970, 250] + ] } } } @@ -68,6 +76,7 @@ Client-side auction endpoint for TSJS library. ``` **Response:** + ```json { "seatbid": [ @@ -76,7 +85,7 @@ Client-side auction endpoint for TSJS library. { "impid": "header-banner", "adm": "...", - "price": 1.50, + "price": 1.5, "w": 728, "h": 90 } @@ -87,6 +96,7 @@ Client-side auction endpoint for TSJS library. ``` **Example:** + ```bash curl -X POST https://edge.example.com/third-party/ad \ -H "Content-Type: application/json" \ @@ -107,6 +117,7 @@ Unified proxy for resources referenced by creatives (images, scripts, CSS, etc.) | `*` | any | No | Original target URL query parameters (preserved as-is) | **Response:** + - **Content-Type:** Mirrors upstream or inferred from content - **Body:** Proxied resource content - HTML responses: Rewritten with creative processor @@ -114,12 +125,14 @@ Unified proxy for resources referenced by creatives (images, scripts, CSS, etc.) - Other: Passed through **Behavior:** + - Validates `tstoken` against reconstructed URL - Follows redirects (301/302/303/307/308, max 4 hops) - Injects synthetic ID as `synthetic_id` query parameter - Logs 1×1 pixel impressions **Example:** + ```bash # Original URL: https://ad.doubleclick.net/pixel?id=123&type=view # Signed proxy URL: @@ -127,6 +140,7 @@ curl "https://edge.example.com/first-party/proxy?tsurl=https://ad.doubleclick.ne ``` **Error Responses:** + - `400 Bad Request` - Missing or invalid `tstoken` - `403 Forbidden` - Token validation failed - `500 Internal Server Error` - Upstream fetch failed @@ -145,16 +159,19 @@ Click tracking redirect endpoint. | `*` | any | No | Original target URL query parameters | **Response:** + - **Status:** `302 Found` - **Location:** Reconstructed target URL with synthetic ID injected **Behavior:** + - Validates `tstoken` against reconstructed URL - Injects `synthetic_id` query parameter - Logs click metadata (tsurl, referer, user agent) - Does not proxy content (redirect only) **Example:** + ```bash curl -I "https://edge.example.com/first-party/click?tsurl=https://advertiser.com/landing&campaign=123&tstoken=xyz..." # → 302 Location: https://advertiser.com/landing?campaign=123&synthetic_id=abc123 @@ -169,11 +186,13 @@ URL signing endpoint. Returns signed first-party proxy URL for a given target UR **Request Methods:** GET or POST **GET Request:** + ```bash curl "https://edge.example.com/first-party/sign?url=https://external.com/pixel.gif" ``` **POST Request:** + ```bash curl -X POST https://edge.example.com/first-party/sign \ -H "Content-Type: application/json" \ @@ -181,6 +200,7 @@ curl -X POST https://edge.example.com/first-party/sign \ ``` **Response:** + ```json { "signed_url": "https://edge.example.com/first-party/proxy?tsurl=https://external.com/pixel.gif&tstoken=abc123..." @@ -188,6 +208,7 @@ curl -X POST https://edge.example.com/first-party/sign \ ``` **Use Cases:** + - TSJS creative runtime (image/iframe proxying) - Dynamic URL signing in client-side code - Testing proxy URL generation @@ -199,6 +220,7 @@ curl -X POST https://edge.example.com/first-party/sign \ URL mutation recovery endpoint. Rebuilds signed proxy URL after creative JavaScript modifies query parameters. **Request Body:** + ```json { "tsclick": "https://edge.example.com/first-party/click?tsurl=https://advertiser.com&campaign=123&tstoken=original...", @@ -210,6 +232,7 @@ URL mutation recovery endpoint. Rebuilds signed proxy URL after creative JavaScr ``` **Response:** + ```json { "url": "https://edge.example.com/first-party/click?tsurl=https://advertiser.com&campaign=123&utm_source=banner&tstoken=new..." @@ -217,6 +240,7 @@ URL mutation recovery endpoint. Rebuilds signed proxy URL after creative JavaScr ``` **Use Cases:** + - TSJS click guard (automatic URL repair) - Handling creative JavaScript that modifies tracking URLs @@ -229,6 +253,7 @@ URL mutation recovery endpoint. Rebuilds signed proxy URL after creative JavaScr Returns active public keys in JWKS (JSON Web Key Set) format for signature verification. **Response:** + ```json { "keys": [ @@ -244,11 +269,13 @@ Returns active public keys in JWKS (JSON Web Key Set) format for signature verif ``` **Example:** + ```bash curl https://edge.example.com/.well-known/ts.jwks.json ``` **Use Cases:** + - Signature verification by downstream systems - Key rotation validation - Integration testing @@ -260,6 +287,7 @@ curl https://edge.example.com/.well-known/ts.jwks.json Verifies a signature against a payload and key ID. **Request Body:** + ```json { "payload": "base64-encoded-data", @@ -269,6 +297,7 @@ Verifies a signature against a payload and key ID. ``` **Response (Success):** + ```json { "verified": true, @@ -278,6 +307,7 @@ Verifies a signature against a payload and key ID. ``` **Response (Failure):** + ```json { "verified": false, @@ -287,6 +317,7 @@ Verifies a signature against a payload and key ID. ``` **Example:** + ```bash curl -X POST https://edge.example.com/verify-signature \ -H "Content-Type: application/json" \ @@ -302,6 +333,7 @@ Generates and activates a new signing key. **Authentication:** Requires basic auth (configured via `handlers` in `trusted-server.toml`) **Request Body (Optional):** + ```json { "kid": "custom-key-id" @@ -311,6 +343,7 @@ Generates and activates a new signing key. If omitted, auto-generates date-based ID (e.g., `ts-2025-01-15-A`). **Response:** + ```json { "new_kid": "ts-2025-01-15-A", @@ -321,6 +354,7 @@ If omitted, auto-generates date-based ID (e.g., `ts-2025-01-15-A`). ``` **Example:** + ```bash curl -X POST https://edge.example.com/admin/keys/rotate \ -u admin:password \ @@ -328,6 +362,7 @@ curl -X POST https://edge.example.com/admin/keys/rotate \ ``` **Behavior:** + - Keeps both new and previous key active - Updates `current-kid` to new key - Preserves old key for graceful transition @@ -343,6 +378,7 @@ Deactivates or deletes a signing key. **Authentication:** Requires basic auth **Request Body:** + ```json { "kid": "ts-2025-01-14-A", @@ -350,12 +386,13 @@ Deactivates or deletes a signing key. } ``` -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `kid` | string | Yes | Key ID to deactivate | -| `delete` | boolean | No | If true, permanently removes key (default: false) | +| Field | Type | Required | Description | +| -------- | ------- | -------- | ------------------------------------------------- | +| `kid` | string | Yes | Key ID to deactivate | +| `delete` | boolean | No | If true, permanently removes key (default: false) | **Response:** + ```json { "kid": "ts-2025-01-14-A", @@ -365,6 +402,7 @@ Deactivates or deletes a signing key. ``` **Example:** + ```bash curl -X POST https://edge.example.com/admin/keys/deactivate \ -u admin:password \ @@ -383,6 +421,7 @@ Serves the TSJS (Trusted Server JavaScript) library. **Path Pattern:** `/static/tsjs=?v=` **Supported Filenames:** + - `tsjs-unified.js` - `tsjs-unified.min.js` @@ -392,17 +431,23 @@ Serves the TSJS (Trusted Server JavaScript) library. | `v` | string | No | Cache-busting hash (SHA256 of bundle contents) | **Response:** + - **Content-Type:** `application/javascript; charset=utf-8` - **Body:** TSJS bundle (IIFE format) - **Headers:** ETag for caching **Example:** + ```html - + ``` **Module Selection:** Controlled at build time via `TSJS_MODULES` environment variable: + ```bash TSJS_MODULES=creative,ext,permutive cargo build ``` @@ -416,21 +461,26 @@ See [Configuration](./configuration.md) for TSJS build options. ### Prebid Integration #### GET /first-party/ad + See [First-Party Endpoints](#get-first-party-ad) above. #### POST /third-party/ad + See [First-Party Endpoints](#post-third-party-ad) above. #### GET /prebid.js (Optional) + Returns empty JavaScript to override Prebid.js when `script_handler` is configured. **Configuration:** + ```toml [integrations.prebid] script_handler = "/prebid.js" ``` **Response:** + - **Content-Type:** `application/javascript; charset=utf-8` - **Body:** `// Prebid.js override by Trusted Server` - **Cache:** `immutable, max-age=31536000` @@ -440,32 +490,40 @@ script_handler = "/prebid.js" ### Permutive Integration #### GET /integrations/permutive/sdk + Serves Permutive SDK from first-party domain. **Response:** + - **Content-Type:** `application/javascript; charset=utf-8` - **Body:** Permutive SDK fetched from `{organization_id}.edge.permutive.app/{workspace_id}-web.js` - **Cache:** 1 hour (configurable via `cache_ttl_seconds`) -#### GET/POST /integrations/permutive/api/* +#### GET/POST /integrations/permutive/api/\* + Proxies to `api.permutive.com`. **Example:** + ```bash curl https://edge.example.com/integrations/permutive/api/settings # → Proxies to https://api.permutive.com/settings ``` -#### GET/POST /integrations/permutive/secure-signal/* +#### GET/POST /integrations/permutive/secure-signal/\* + Proxies to `secure-signals.permutive.app`. -#### GET/POST /integrations/permutive/events/* +#### GET/POST /integrations/permutive/events/\* + Proxies to `events.permutive.app` for event tracking. -#### GET/POST /integrations/permutive/sync/* +#### GET/POST /integrations/permutive/sync/\* + Proxies to `sync.permutive.com` for ID synchronization. -#### GET /integrations/permutive/cdn/* +#### GET /integrations/permutive/cdn/\* + Proxies to `cdn.permutive.com` for static assets. --- @@ -473,17 +531,17 @@ Proxies to `cdn.permutive.com` for static assets. ### Testlight Integration #### POST /integrations/testlight/auction + Testing auction endpoint with synthetic ID injection. **Request Body:** + ```json { "user": { "id": null }, - "imp": [ - { "id": "slot-1" } - ] + "imp": [{ "id": "slot-1" }] } ``` @@ -491,6 +549,7 @@ Testing auction endpoint with synthetic ID injection. Proxies to configured endpoint with `user.id` populated with synthetic ID. **Response Headers:** + - `X-Synthetic-Trusted-Server` - Stable synthetic ID - `X-Synthetic-Fresh` - One-time fresh ID @@ -530,6 +589,7 @@ All endpoints use consistent error response format: Endpoints under protected paths require HTTP Basic Authentication: **Configuration:** + ```toml [[handlers]] path = "^/admin" @@ -538,11 +598,13 @@ password = "secure-password" ``` **Usage:** + ```bash curl -u admin:secure-password https://edge.example.com/admin/keys/rotate ``` **Protected Endpoints:** + - `/admin/keys/rotate` - `/admin/keys/deactivate` - Any paths matching configured `handlers` patterns @@ -554,6 +616,7 @@ curl -u admin:secure-password https://edge.example.com/admin/keys/rotate Trusted Server relies on Fastly's built-in rate limiting. Configure in Fastly dashboard: **Recommended Limits:** + - Public endpoints: 1000 req/min per IP - Admin endpoints: 10 req/min per IP - TSJS serving: 10000 req/min (highly cacheable) diff --git a/docs/guide/architecture.md b/docs/guide/architecture.md index 822b302..211de37 100644 --- a/docs/guide/architecture.md +++ b/docs/guide/architecture.md @@ -35,6 +35,7 @@ Trusted Server is built as a Rust-based edge computing application that runs on ### trusted-server-common Core library containing shared functionality: + - Synthetic ID generation - Cookie handling - HTTP abstractions @@ -44,6 +45,7 @@ Core library containing shared functionality: ### trusted-server-fastly Fastly-specific implementation: + - Main application entry point - Fastly SDK integration - Request/response handling @@ -86,6 +88,7 @@ All tracking operations require explicit GDPR consent checks before execution. ### Fastly KV Store Used for: + - Counter storage - Domain mappings - Configuration cache @@ -112,6 +115,7 @@ User data is not persisted in storage - only processed in-flight at the edge. ## WebAssembly Target Compiled to `wasm32-wasip1` for Fastly Compute: + - Sandboxed execution - Fast cold starts - Efficient resource usage diff --git a/docs/guide/configuration-reference.md b/docs/guide/configuration-reference.md index c820ca7..2fa4cc9 100644 --- a/docs/guide/configuration-reference.md +++ b/docs/guide/configuration-reference.md @@ -35,6 +35,7 @@ TRUSTED_SERVER__SECTION__SUBSECTION__FIELD ``` **Rules**: + - Prefix: `TRUSTED_SERVER` - Separator: `__` (double underscore) - Case: UPPERCASE @@ -43,27 +44,32 @@ TRUSTED_SERVER__SECTION__SUBSECTION__FIELD ### Examples **Simple Field**: + ```bash TRUSTED_SERVER__PUBLISHER__DOMAIN=publisher.com ``` **Nested Field**: + ```bash TRUSTED_SERVER__INTEGRATIONS__PREBID__SERVER_URL=https://prebid.example/auction ``` **Array Field (JSON)**: + ```bash TRUSTED_SERVER__INTEGRATIONS__PREBID__BIDDERS='["kargo","rubicon"]' ``` **Array Field (Indexed)**: + ```bash TRUSTED_SERVER__INTEGRATIONS__PREBID__BIDDERS__0=kargo TRUSTED_SERVER__INTEGRATIONS__PREBID__BIDDERS__1=rubicon ``` **Array Field (Comma-Separated)**: + ```bash TRUSTED_SERVER__INTEGRATIONS__PREBID__BIDDERS=kargo,rubicon,appnexus ``` @@ -74,14 +80,15 @@ Core publisher settings for domain, origin, and proxy configuration. ### `[publisher]` -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `domain` | String | Yes | Publisher's domain name | -| `cookie_domain` | String | Yes | Domain for setting cookies (typically with leading dot) | -| `origin_url` | String | Yes | Full URL of publisher origin server | -| `proxy_secret` | String | Yes | Secret key for encrypting/signing proxy URLs | +| Field | Type | Required | Description | +| --------------- | ------ | -------- | ------------------------------------------------------- | +| `domain` | String | Yes | Publisher's domain name | +| `cookie_domain` | String | Yes | Domain for setting cookies (typically with leading dot) | +| `origin_url` | String | Yes | Full URL of publisher origin server | +| `proxy_secret` | String | Yes | Secret key for encrypting/signing proxy URLs | **Example**: + ```toml [publisher] domain = "publisher.com" @@ -91,6 +98,7 @@ proxy_secret = "change-me-to-secure-random-value" ``` **Environment Override**: + ```bash TRUSTED_SERVER__PUBLISHER__DOMAIN=publisher.com TRUSTED_SERVER__PUBLISHER__COOKIE_DOMAIN=.publisher.com @@ -105,13 +113,15 @@ TRUSTED_SERVER__PUBLISHER__PROXY_SECRET=your-secret-here **Purpose**: Primary domain for the publisher. **Usage**: + - Displayed in synthetic ID generation - Used in template variables (`publisher_domain`) - Part of request context **Format**: Hostname without protocol or path + - ✅ `publisher.com` -- ✅ `www.publisher.com` +- ✅ `www.publisher.com` - ❌ `https://publisher.com` - ❌ `publisher.com/path` @@ -120,10 +130,12 @@ TRUSTED_SERVER__PUBLISHER__PROXY_SECRET=your-secret-here **Purpose**: Domain scope for synthetic ID cookies. **Usage**: + - Set on `synthetic_id` cookie - Controls cookie sharing across subdomains **Format**: Domain with optional leading dot + - `.publisher.com` - Shares across all subdomains - `publisher.com` - Exact domain only @@ -134,11 +146,13 @@ TRUSTED_SERVER__PUBLISHER__PROXY_SECRET=your-secret-here **Purpose**: Backend origin server URL for publisher content. **Usage**: + - Fallback proxy target for non-integration requests - HTML processing rewrites origin URLs to request host - Base for relative URL resolution **Format**: Full URL with protocol + - ✅ `https://origin.publisher.com` - ✅ `https://origin.publisher.com:8080` - ✅ `http://192.168.1.1:9000` @@ -151,18 +165,21 @@ TRUSTED_SERVER__PUBLISHER__PROXY_SECRET=your-secret-here **Purpose**: Secret key for HMAC-SHA256 signing of proxy URLs. **Security**: + - Keep confidential and secure - Rotate periodically (90 days recommended) - Use cryptographically random values (32+ bytes) - Never commit to version control **Generation**: + ```bash # Generate secure random secret openssl rand -base64 32 ``` **Usage**: + - Signs `/first-party/proxy` URLs - Signs `/first-party/click` URLs - Validates incoming proxy requests @@ -178,14 +195,15 @@ Settings for generating privacy-preserving synthetic identifiers. ### `[synthetic]` -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `counter_store` | String | Yes | Fastly KV store name for counters | -| `opid_store` | String | Yes | Fastly KV store name for publisher ID mappings | -| `secret_key` | String | Yes | HMAC secret for ID generation | -| `template` | String | Yes | Handlebars template for ID composition | +| Field | Type | Required | Description | +| --------------- | ------ | -------- | ---------------------------------------------- | +| `counter_store` | String | Yes | Fastly KV store name for counters | +| `opid_store` | String | Yes | Fastly KV store name for publisher ID mappings | +| `secret_key` | String | Yes | HMAC secret for ID generation | +| `template` | String | Yes | Handlebars template for ID composition | **Example**: + ```toml [synthetic] counter_store = "counter_store" @@ -195,6 +213,7 @@ template = "{{ client_ip }}:{{ user_agent }}:{{ first_party_id }}" ``` **Environment Override**: + ```bash TRUSTED_SERVER__SYNTHETIC__COUNTER_STORE=counter_store TRUSTED_SERVER__SYNTHETIC__OPID_STORE=opid_store @@ -209,17 +228,20 @@ TRUSTED_SERVER__SYNTHETIC__TEMPLATE="{{ client_ip }}:{{ user_agent }}" **Purpose**: Fastly KV store for synthetic ID counters. **Usage**: + - Stores incrementing counters per domain - Ensures ID uniqueness - Accessed via Fastly KV Store API **Setup**: + ```bash # Create KV store fastly kv-store create --name=counter_store ``` **Data Format**: + ```json { "publisher.com": 12345, @@ -232,17 +254,20 @@ fastly kv-store create --name=counter_store **Purpose**: Fastly KV store for publisher-provided ID mappings. **Usage**: + - Maps publisher IDs to synthetic IDs - Enables first-party ID integration - Optional (used if publisher provides IDs) **Setup**: + ```bash # Create KV store fastly kv-store create --name=opid_store ``` **Data Format**: + ```json { "publisher-id-123": "synthetic-abc", @@ -255,18 +280,21 @@ fastly kv-store create --name=opid_store **Purpose**: HMAC secret for deterministic ID generation. **Security**: + - Minimum 8 bytes (validation enforced) - Cannot be `"secret-key"` (reserved/invalid) - Rotate periodically for security - Store securely (environment variable recommended) **Generation**: + ```bash # Generate secure random key openssl rand -hex 32 ``` **Validation**: Application startup fails if: + - Empty string - Exactly `"secret-key"` (default placeholder) - Less than 1 character @@ -277,28 +305,31 @@ openssl rand -hex 32 **Available Variables**: -| Variable | Description | Example | -|----------|-------------|---------| -| `client_ip` | Client IP address | `192.168.1.1` | -| `user_agent` | User-Agent header | `Mozilla/5.0...` | -| `first_party_id` | Publisher-provided ID | `user-123` | -| `auth_user_id` | Authenticated user ID | `auth-456` | -| `publisher_domain` | Publisher domain | `publisher.com` | -| `accept_language` | Accept-Language header | `en-US,en;q=0.9` | +| Variable | Description | Example | +| ------------------ | ---------------------- | ---------------- | +| `client_ip` | Client IP address | `192.168.1.1` | +| `user_agent` | User-Agent header | `Mozilla/5.0...` | +| `first_party_id` | Publisher-provided ID | `user-123` | +| `auth_user_id` | Authenticated user ID | `auth-456` | +| `publisher_domain` | Publisher domain | `publisher.com` | +| `accept_language` | Accept-Language header | `en-US,en;q=0.9` | **Template Examples**: **Simple (IP + UA)**: + ```toml template = "{{ client_ip }}:{{ user_agent }}" ``` **With First-Party ID**: + ```toml template = "{{ first_party_id }}:{{ client_ip }}" ``` **Comprehensive**: + ```toml template = "{{ client_ip }}:{{ user_agent }}:{{ first_party_id }}:{{ auth_user_id }}:{{ publisher_domain }}:{{ accept_language }}" ``` @@ -307,10 +338,11 @@ template = "{{ client_ip }}:{{ user_agent }}:{{ first_party_id }}:{{ auth_user_i ::: tip Template Design Choose template variables based on your privacy and uniqueness requirements: + - **More variables** = More unique IDs, less privacy - **Fewer variables** = More privacy, potential collisions - **Include `first_party_id`** for publisher ID integration -::: + ::: ## Response Headers @@ -323,6 +355,7 @@ Custom headers added to all responses. **Format**: Key-value pairs **Example**: + ```toml [response_headers] X-Custom-Header = "custom value" @@ -332,11 +365,13 @@ Cache-Control = "public, max-age=3600" ``` **Environment Override**: + ```bash TRUSTED_SERVER__RESPONSE_HEADERS__X_CUSTOM_HEADER="custom value" ``` **Use Cases**: + - Custom tracking headers - Cache control overrides - Debugging identifiers @@ -352,13 +387,14 @@ Configuration for Ed25519 request signing and JWKS management. ### `[request_signing]` -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `enabled` | Boolean | No (default: false) | Enable request signing features | -| `config_store_id` | String | If enabled | Fastly Config Store ID for JWKS | -| `secret_store_id` | String | If enabled | Fastly Secret Store ID for private keys | +| Field | Type | Required | Description | +| ----------------- | ------- | ------------------- | --------------------------------------- | +| `enabled` | Boolean | No (default: false) | Enable request signing features | +| `config_store_id` | String | If enabled | Fastly Config Store ID for JWKS | +| `secret_store_id` | String | If enabled | Fastly Secret Store ID for private keys | **Example**: + ```toml [request_signing] enabled = true @@ -367,6 +403,7 @@ secret_store_id = "01GYYY" # From Fastly dashboard ``` **Environment Override**: + ```bash TRUSTED_SERVER__REQUEST_SIGNING__ENABLED=true TRUSTED_SERVER__REQUEST_SIGNING__CONFIG_STORE_ID=01GXXX @@ -376,6 +413,7 @@ TRUSTED_SERVER__REQUEST_SIGNING__SECRET_STORE_ID=01GYYY ### Store Setup **Config Store** (for public keys): + ```bash # Create store fastly config-store create --name=jwks_store @@ -385,6 +423,7 @@ fastly config-store list ``` **Secret Store** (for private keys): + ```bash # Create store fastly secret-store create --name=signing_keys @@ -394,6 +433,7 @@ fastly secret-store list ``` **Link to Service** (`fastly.toml`): + ```toml [setup.config_stores.jwks_store] @@ -412,13 +452,14 @@ Path-based HTTP Basic Authentication. **Format**: Array of handler objects -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `path` | String (Regex) | Yes | Regular expression matching paths | -| `username` | String | Yes | HTTP Basic Auth username | -| `password` | String | Yes | HTTP Basic Auth password | +| Field | Type | Required | Description | +| ---------- | -------------- | -------- | --------------------------------- | +| `path` | String (Regex) | Yes | Regular expression matching paths | +| `username` | String | Yes | HTTP Basic Auth username | +| `password` | String | Yes | HTTP Basic Auth password | **Example**: + ```toml # Single handler [[handlers]] @@ -439,6 +480,7 @@ password = "api-pass" ``` **Environment Override**: + ```bash # Handler 0 TRUSTED_SERVER__HANDLERS__0__PATH="^/admin" @@ -479,12 +521,14 @@ path = "^/api/v[0-9]+/private" # /api/v1/private, /api/v2/private ### Security Considerations **Password Storage**: + - Stored in plain text in config - Use environment variables in production - Rotate passwords regularly - Consider using Fastly Secret Store **Limitations**: + - HTTP Basic Auth (not OAuth/JWT) - Single username/password per path - No role-based access control @@ -492,9 +536,11 @@ path = "^/api/v[0-9]+/private" # /api/v1/private, /api/v2/private ::: warning Production Use For production, store credentials in environment variables: + ```bash TRUSTED_SERVER__HANDLERS__0__PASSWORD=$(cat /run/secrets/admin_password) ``` + ::: ## URL Rewrite Configuration @@ -503,11 +549,12 @@ Control which domains are excluded from first-party rewriting. ### `[rewrite]` -| Field | Type | Required | Description | -|-------|------|----------|-------------| +| Field | Type | Required | Description | +| ----------------- | ------------- | ---------------- | ------------------------- | | `exclude_domains` | Array[String] | No (default: []) | Domains to skip rewriting | **Example**: + ```toml [rewrite] exclude_domains = [ @@ -518,6 +565,7 @@ exclude_domains = [ ``` **Environment Override**: + ```bash # JSON array TRUSTED_SERVER__REWRITE__EXCLUDE_DOMAINS='["*.cdn.example.com","localhost"]' @@ -533,20 +581,26 @@ TRUSTED_SERVER__REWRITE__EXCLUDE_DOMAINS="*.cdn.example.com,localhost" ### Pattern Matching **Wildcard Patterns** (`*`): + ```toml "*.cdn.example.com" ``` + Matches: + - ✅ `assets.cdn.example.com` - ✅ `images.cdn.example.com` - ✅ `cdn.example.com` (base domain) - ❌ `cdn.example.com.evil.com` (different domain) **Exact Patterns** (no `*`): + ```toml "api.example.com" ``` + Matches: + - ✅ `api.example.com` - ❌ `www.api.example.com` - ❌ `api.example.com.evil.com` @@ -554,21 +608,25 @@ Matches: ### Use Cases **Trusted Partners**: + ```toml exclude_domains = ["*.approved-cdn.com"] ``` **First-Party Resources**: + ```toml exclude_domains = ["assets.publisher.com", "static.publisher.com"] ``` **Development**: + ```toml exclude_domains = ["localhost", "127.0.0.1", "*.local"] ``` **Performance** (already first-party): + ```toml exclude_domains = ["*.publisher.com"] # Skip unnecessary proxying ``` @@ -583,25 +641,26 @@ Settings for built-in integrations (Prebid, Next.js, Permutive, Testlight). All integrations support: -| Field | Type | Description | -|-------|------|-------------| +| Field | Type | Description | +| --------- | ------- | ------------------------------------------- | | `enabled` | Boolean | Enable/disable integration (default: false) | ### Prebid Integration **Section**: `[integrations.prebid]` -| Field | Type | Default | Description | -|-------|------|---------|-------------| -| `enabled` | Boolean | `false` | Enable Prebid integration | -| `server_url` | String | Required | Prebid Server endpoint URL | -| `timeout_ms` | Integer | `1000` | Request timeout in milliseconds | -| `bidders` | Array[String] | `[]` | List of enabled bidders | -| `auto_configure` | Boolean | `false` | Auto-inject Prebid.js shim | -| `debug` | Boolean | `false` | Enable debug logging | -| `script_handler` | String | Optional | Custom script endpoint path | +| Field | Type | Default | Description | +| ---------------- | ------------- | -------- | ------------------------------- | +| `enabled` | Boolean | `false` | Enable Prebid integration | +| `server_url` | String | Required | Prebid Server endpoint URL | +| `timeout_ms` | Integer | `1000` | Request timeout in milliseconds | +| `bidders` | Array[String] | `[]` | List of enabled bidders | +| `auto_configure` | Boolean | `false` | Auto-inject Prebid.js shim | +| `debug` | Boolean | `false` | Enable debug logging | +| `script_handler` | String | Optional | Custom script endpoint path | **Example**: + ```toml [integrations.prebid] enabled = true @@ -613,6 +672,7 @@ debug = false ``` **Environment Override**: + ```bash TRUSTED_SERVER__INTEGRATIONS__PREBID__ENABLED=true TRUSTED_SERVER__INTEGRATIONS__PREBID__SERVER_URL=https://prebid.example/auction @@ -626,12 +686,13 @@ TRUSTED_SERVER__INTEGRATIONS__PREBID__DEBUG=false **Section**: `[integrations.nextjs]` -| Field | Type | Default | Description | -|-------|------|---------|-------------| -| `enabled` | Boolean | `false` | Enable Next.js integration | -| `rewrite_attributes` | Array[String] | `["href","link","url"]` | Attributes to rewrite | +| Field | Type | Default | Description | +| -------------------- | ------------- | ----------------------- | -------------------------- | +| `enabled` | Boolean | `false` | Enable Next.js integration | +| `rewrite_attributes` | Array[String] | `["href","link","url"]` | Attributes to rewrite | **Example**: + ```toml [integrations.nextjs] enabled = true @@ -639,6 +700,7 @@ rewrite_attributes = ["href", "link", "url", "src"] ``` **Environment Override**: + ```bash TRUSTED_SERVER__INTEGRATIONS__NEXTJS__ENABLED=true TRUSTED_SERVER__INTEGRATIONS__NEXTJS__REWRITE_ATTRIBUTES=href,link,url,src @@ -648,18 +710,19 @@ TRUSTED_SERVER__INTEGRATIONS__NEXTJS__REWRITE_ATTRIBUTES=href,link,url,src **Section**: `[integrations.permutive]` -| Field | Type | Default | Description | -|-------|------|---------|-------------| -| `enabled` | Boolean | `false` | Enable Permutive integration | -| `organization_id` | String | Required | Permutive organization ID | -| `workspace_id` | String | Required | Permutive workspace ID | -| `project_id` | String | Required | Permutive project ID | -| `api_endpoint` | String | `https://api.permutive.com` | Permutive API URL | -| `secure_signals_endpoint` | String | `https://secure-signals.permutive.app` | Secure signals URL | -| `cache_ttl_seconds` | Integer | `3600` | Cache TTL in seconds | -| `rewrite_sdk` | Boolean | `true` | Rewrite Permutive SDK references | +| Field | Type | Default | Description | +| ------------------------- | ------- | -------------------------------------- | -------------------------------- | +| `enabled` | Boolean | `false` | Enable Permutive integration | +| `organization_id` | String | Required | Permutive organization ID | +| `workspace_id` | String | Required | Permutive workspace ID | +| `project_id` | String | Required | Permutive project ID | +| `api_endpoint` | String | `https://api.permutive.com` | Permutive API URL | +| `secure_signals_endpoint` | String | `https://secure-signals.permutive.app` | Secure signals URL | +| `cache_ttl_seconds` | Integer | `3600` | Cache TTL in seconds | +| `rewrite_sdk` | Boolean | `true` | Rewrite Permutive SDK references | **Example**: + ```toml [integrations.permutive] enabled = true @@ -676,14 +739,15 @@ rewrite_sdk = true **Section**: `[integrations.testlight]` -| Field | Type | Default | Description | -|-------|------|---------|-------------| -| `enabled` | Boolean | `false` | Enable Testlight integration | -| `endpoint` | String | Required | Testlight auction endpoint | -| `timeout_ms` | Integer | `1000` | Request timeout in milliseconds | -| `rewrite_scripts` | Boolean | `true` | Rewrite Testlight script references | +| Field | Type | Default | Description | +| ----------------- | ------- | -------- | ----------------------------------- | +| `enabled` | Boolean | `false` | Enable Testlight integration | +| `endpoint` | String | Required | Testlight auction endpoint | +| `timeout_ms` | Integer | `1000` | Request timeout in milliseconds | +| `rewrite_scripts` | Boolean | `true` | Rewrite Testlight script references | **Example**: + ```toml [integrations.testlight] enabled = true @@ -699,26 +763,31 @@ rewrite_scripts = true Configuration is validated at startup: **Publisher Validation**: + - All fields non-empty - `origin_url` is valid URL **Synthetic Validation**: + - `secret_key` ≥ 1 character - `secret_key` ≠ `"secret-key"` - `template` non-empty **Handler Validation**: + - `path` is valid regex - `username` non-empty - `password` non-empty **Integration Validation**: + - Each integration implements `Validate` trait - Custom rules per integration ### Validation Errors **Startup Failure** if: + - Required fields missing - Invalid data types - Regex compilation fails @@ -726,8 +795,9 @@ Configuration is validated at startup: - Integration config fails validation **Error Format**: + ``` -Configuration error: Integration 'prebid' configuration failed validation: +Configuration error: Integration 'prebid' configuration failed validation: server_url: must not be empty ``` @@ -736,6 +806,7 @@ server_url: must not be empty ### Configuration Management **Development**: + ```toml # trusted-server.dev.toml [publisher] @@ -745,6 +816,7 @@ proxy_secret = "dev-secret" ``` **Staging**: + ```bash # .env.staging TRUSTED_SERVER__PUBLISHER__ORIGIN_URL=https://staging.publisher.com @@ -752,6 +824,7 @@ TRUSTED_SERVER__PUBLISHER__PROXY_SECRET=$(cat /run/secrets/proxy_secret_staging) ``` **Production**: + ```bash # All secrets from environment TRUSTED_SERVER__PUBLISHER__PROXY_SECRET=$(cat /run/secrets/proxy_secret) @@ -766,18 +839,19 @@ TRUSTED_SERVER__HANDLERS__0__PASSWORD=$(cat /run/secrets/admin_password) ✅ Rotate secrets periodically ✅ Generate cryptographically random values ✅ Store in secure secret management (Fastly Secret Store, Vault) -✅ Use different secrets per environment +✅ Use different secrets per environment **Don't**: ❌ Commit secrets to version control ❌ Use default/placeholder values ❌ Share secrets across environments ❌ Log secret values -❌ Expose in error messages +❌ Expose in error messages ### File Organization **Recommended Structure**: + ``` trusted-server.toml # Base config trusted-server.dev.toml # Development overrides @@ -788,6 +862,7 @@ trusted-server.dev.toml # Development overrides ``` **.gitignore**: + ``` .env.production .env.staging @@ -800,26 +875,31 @@ trusted-server.dev.toml # Development overrides ### Common Issues **"Failed to build configuration"**: + - Check TOML syntax (trailing commas, quotes) - Verify all required fields present - Check environment variable format **"Secret key is not valid"**: + - Cannot use `"secret-key"` (placeholder) - Must be non-empty - Change to secure random value **"Invalid regex"**: + - Handler `path` must be valid regex - Test pattern: `echo "^/admin" | grep -E "^/admin"` - Escape special characters: `\.`, `\$`, etc. **"Integration configuration could not be parsed"**: + - Check JSON syntax in env vars - Verify indexed arrays (0, 1, 2...) - Check field names match exactly **Environment Variables Not Applied**: + - Verify prefix: `TRUSTED_SERVER__` - Check separator: `__` (double underscore) - Confirm variable is exported: `echo $VARIABLE_NAME` @@ -828,6 +908,7 @@ trusted-server.dev.toml # Development overrides ### Debug Configuration **Print Loaded Config** (test only): + ```rust use trusted_server_common::settings::Settings; @@ -836,12 +917,14 @@ println!("{:#?}", settings); ``` **Check Environment**: + ```bash # List all TRUSTED_SERVER variables env | grep TRUSTED_SERVER ``` **Validate TOML**: + ```bash # Use any TOML validator cat trusted-server.toml | npx toml-cli validate diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 962e440..5509fae 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -59,6 +59,7 @@ Main application configuration file. **Format**: TOML (Tom's Obvious, Minimal Language) **Sections**: + - `[publisher]` - Publisher domain and origin settings - `[synthetic]` - Synthetic ID generation - `[request_signing]` - Request signing and JWKS @@ -68,6 +69,7 @@ Main application configuration file. - `[integrations.*]` - Integration configs (Prebid, Next.js, etc.) **Example**: + ```toml [publisher] domain = "publisher.com" @@ -105,6 +107,7 @@ Fastly Compute service configuration. **Purpose**: Build settings, local development, store links **Example**: + ```toml manifest_version = 2 name = "trusted-server" @@ -115,7 +118,7 @@ language = "rust" [local_server] [local_server.kv_stores.counter_store] file = "test-data/counter_store.json" - + [local_server.kv_stores.opid_store] file = "test-data/opid_store.json" @@ -129,6 +132,7 @@ language = "rust" Environment-specific variable files. **`.env.dev`** - Local development: + ```bash TRUSTED_SERVER__PUBLISHER__ORIGIN_URL=http://localhost:3000 TRUSTED_SERVER__SYNTHETIC__SECRET_KEY=dev-secret @@ -136,16 +140,20 @@ LOG_LEVEL=debug ``` **`.env.staging`** - Staging environment: + ```bash TRUSTED_SERVER__PUBLISHER__ORIGIN_URL=https://staging.publisher.com -TRUSTED_SERVER__SYNTHETIC__SECRET_KEY=$(cat /run/secrets/synthetic_key_staging) +# Secrets should be set via CI/CD or secret management, not in .env files +# TRUSTED_SERVER__SYNTHETIC__SECRET_KEY= ``` **`.env.production`** - Production (secrets from secure store): + ```bash -TRUSTED_SERVER__PUBLISHER__PROXY_SECRET=$(cat /run/secrets/proxy_secret) -TRUSTED_SERVER__SYNTHETIC__SECRET_KEY=$(cat /run/secrets/synthetic_secret) TRUSTED_SERVER__REQUEST_SIGNING__ENABLED=true +# Secrets should be injected by your deployment system +# TRUSTED_SERVER__PUBLISHER__PROXY_SECRET= +# TRUSTED_SERVER__SYNTHETIC__SECRET_KEY= ``` ## Environment Variables @@ -157,6 +165,7 @@ TRUSTED_SERVER__SECTION__FIELD=value ``` **Rules**: + - Prefix: `TRUSTED_SERVER` - Separator: `__` (double underscore) - Case: UPPERCASE @@ -165,22 +174,26 @@ TRUSTED_SERVER__SECTION__FIELD=value ### Examples **Simple Field**: + ```bash TRUSTED_SERVER__PUBLISHER__DOMAIN=publisher.com ``` **Array (JSON)**: + ```bash TRUSTED_SERVER__INTEGRATIONS__PREBID__BIDDERS='["kargo","rubicon"]' ``` **Array (Indexed)**: + ```bash TRUSTED_SERVER__INTEGRATIONS__PREBID__BIDDERS__0=kargo TRUSTED_SERVER__INTEGRATIONS__PREBID__BIDDERS__1=rubicon ``` **Array (Comma-Separated)**: + ```bash TRUSTED_SERVER__INTEGRATIONS__PREBID__BIDDERS=kargo,rubicon,appnexus ``` @@ -200,6 +213,7 @@ proxy_secret = "secure-random-secret" ``` **Key Fields**: + - `domain` - Your publisher domain - `cookie_domain` - Domain for synthetic ID cookies (use `.domain.com` for subdomains) - `origin_url` - Backend origin server URL @@ -207,9 +221,11 @@ proxy_secret = "secure-random-secret" ::: warning Security Generate `proxy_secret` with cryptographically random values: + ```bash openssl rand -base64 32 ``` + ::: ### Synthetic IDs @@ -225,6 +241,7 @@ template = "{{ client_ip }}:{{ user_agent }}:{{ first_party_id }}" ``` **Template Variables**: + - `client_ip` - Client IP address - `user_agent` - User-Agent header - `first_party_id` - Publisher-provided ID @@ -246,6 +263,7 @@ secret_store_id = "01GYYY" # From Fastly dashboard ``` **Setup**: + 1. Create Fastly Config Store for JWKS 2. Create Fastly Secret Store for private keys 3. Copy store IDs to configuration @@ -258,6 +276,7 @@ See [Request Signing](/guide/request-signing) and [Key Rotation](/guide/key-rota Configure built-in integrations. **Prebid**: + ```toml [integrations.prebid] enabled = true @@ -268,6 +287,7 @@ auto_configure = false ``` **Next.js**: + ```toml [integrations.nextjs] enabled = true @@ -275,6 +295,7 @@ rewrite_attributes = ["href", "link", "url"] ``` **Permutive**: + ```toml [integrations.permutive] enabled = true @@ -300,6 +321,7 @@ fastly kv-store create --name=opid_store ``` **Link to Service** (`fastly.toml`): + ```toml [local_server.kv_stores.counter_store] file = "test-data/counter_store.json" @@ -339,6 +361,7 @@ fastly secret-store list Configuration is validated at application startup: **Checks**: + - Required fields present - Data types correct - Regex patterns valid @@ -364,16 +387,18 @@ fastly compute serve ### Best Practices **Development**: + ```bash # Use simple secrets for local dev TRUSTED_SERVER__PUBLISHER__PROXY_SECRET=dev-secret ``` **Staging/Production**: + ```bash -# Load from secure sources -TRUSTED_SERVER__PUBLISHER__PROXY_SECRET=$(cat /run/secrets/proxy_secret) -TRUSTED_SERVER__SYNTHETIC__SECRET_KEY=$(vault kv get -field=value secret/synthetic_key) +# Inject secrets via your CI/CD or secret manager +TRUSTED_SERVER__PUBLISHER__PROXY_SECRET= +TRUSTED_SERVER__SYNTHETIC__SECRET_KEY= ``` **Do**: @@ -381,13 +406,13 @@ TRUSTED_SERVER__SYNTHETIC__SECRET_KEY=$(vault kv get -field=value secret/synthet ✅ Generate cryptographically random values ✅ Rotate secrets periodically ✅ Store in Fastly Secret Store or Vault -✅ Use different secrets per environment +✅ Use different secrets per environment **Don't**: ❌ Commit secrets to version control ❌ Use default placeholder values ❌ Share secrets across environments -❌ Log secret values +❌ Log secret values ### `.gitignore` @@ -406,11 +431,11 @@ trusted-server.production.toml ### Multi-Environment Setup **Directory Structure**: + ``` -project/ +trusted-server/ ├── trusted-server.toml # Base config -├── trusted-server.dev.toml # Development overrides -├── .env.development # Dev environment vars +├── .env.dev # Dev environment vars ├── .env.staging # Staging environment vars ├── .env.production # Production (not in git) ├── .env.example # Template (in git) @@ -418,6 +443,7 @@ project/ ``` **Base Config** (`trusted-server.toml`): + ```toml # Shared across all environments [synthetic] @@ -429,6 +455,7 @@ bidders = ["kargo", "rubicon"] ``` **Environment Overrides**: + ```bash # Development export TRUSTED_SERVER__PUBLISHER__ORIGIN_URL=http://localhost:3000 @@ -460,21 +487,25 @@ TRUSTED_SERVER__INTEGRATIONS__PREBID__SERVER_URL=https://new-prebid.com/auction ### Common Issues **"Failed to build configuration"**: + - Check TOML syntax (commas, quotes, brackets) - Verify all required fields present - Check environment variable format **"Secret key is not valid"**: + - Cannot use `"secret-key"` placeholder - Must be non-empty - Change to secure random value **"Invalid regex"**: + - Handler `path` must be valid regex - Escape special characters: `\.`, `\$` - Test with: `echo "pattern" | grep -E "pattern"` **Environment variables not applied**: + - Verify prefix: `TRUSTED_SERVER__` - Check separator: `__` (double underscore) - Confirm exported: `echo $VAR_NAME` @@ -482,16 +513,19 @@ TRUSTED_SERVER__INTEGRATIONS__PREBID__SERVER_URL=https://new-prebid.com/auction ### Debug Commands **Check environment**: + ```bash env | grep TRUSTED_SERVER ``` **Validate TOML**: + ```bash cat trusted-server.toml | npx toml-cli validate ``` **Test local server**: + ```bash fastly compute serve --verbose ``` diff --git a/docs/guide/creative-processing.md b/docs/guide/creative-processing.md index f7bca18..1ef3fc1 100644 --- a/docs/guide/creative-processing.md +++ b/docs/guide/creative-processing.md @@ -58,22 +58,32 @@ When `with_streaming()` is enabled in `ProxyRequestConfig`, HTML/CSS processing **Elements**: ``, `` **Attributes**: + - `src` - Primary image source - `data-src` - Lazy-loading source - `srcset` - Responsive image sources - `imagesrcset` - Image set (used in ``) **Example**: + ```html - + - + ``` ### Scripts @@ -81,9 +91,11 @@ When `with_streaming()` is enabled in `ProxyRequestConfig`, HTML/CSS processing **Elements**: ` @@ -97,18 +109,23 @@ When `with_streaming()` is enabled in `ProxyRequestConfig`, HTML/CSS processing **Elements**: `