Skip to content
Draft
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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ default-members = [
debug = 1

[workspace.dependencies]
async-trait = "0.1"
base64 = "0.22"
brotli = "8.0"
bytes = "1.10"
Expand All @@ -34,13 +35,14 @@ handlebars = "6.3.2"
hex = "0.4.3"
hmac = "0.12.1"
http = "1.3.1"
jose-jwk = "0.1.2"
log = "0.4.28"
log-fastly = "0.11.9"
lol_html = "2.7.0"
once_cell = "1.19"
pin-project-lite = "0.2"
regex = "1.12.2"
jose-jwk = "0.1.2"
rand = "0.8"
regex = "1.12.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.145"
sha2 = "0.10.9"
Expand Down
3 changes: 1 addition & 2 deletions FAQ_POC.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ NOT all the capabilities. Tech Lab will build and support core services to enabl
**Does the Trusteed Server preclude using third party tags?**
No, it does not. You should be able to begin migrating certain modules and parts of your content and experience as you go, without a forklift upgrade. We plan to support server side tagging capabilities to enable third party support.

**Why are you only using two vendors in the POC?**
**Why are you only using two partners in the POC?**
Fastly and Equativ volunteered time and resources to us and they fit the technical needs and requirements for Trusted Server. For the sake of getting to market ASAP, we chose to double down on these two partners. We do not play favorites or have any financial incentive with these two companies and will begin implementing on other partners in the near future. Any ad exchange supporting prebid server requests should already find support. We will prioritize modules for other edge cloud providers based on industry priorities

**How will this project be managed?**
Expand All @@ -39,4 +39,3 @@ Yes. As long as your managed service provider can separate the edge from the CMS

**How will this comply with Privacy regulations?**
The trusted server will have modules to support Consent Management Providers (CMP) and send the GPP or TCF string as required in the ad request.

89 changes: 55 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
# Trusted Server
# 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.
: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 the new execution layer for the open-web, returning control of 1st party data, security, and overall user-experience back to publishers.

At this time, Trusted Server is designed to work with Fastly Compute. Follow these steps to configure Fastly Compute and deploy it.

## Getting Started: Edge-Cloud Support on Fastly

- 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

- 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 partner 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_partner_1”
-
- 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

- 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”
-

: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.

Expand All @@ -38,28 +39,33 @@ At this time, Trusted Server is designed to work with Fastly Compute. Follow the
```sh
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```

### Fastly CLI

#### Install Fastly CLI
#### Install Fastly CLI

```sh
brew install fastly/tap/fastly
```

#### Verify Installation and Version
#### 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

```sh
fastly profile create
```

### Rust

#### Install Rust with asdf (our preference)

```sh
brew install asdf
asdf plugin add rust
Expand All @@ -70,6 +76,7 @@ asdf reshim
### NodeJS

#### Install NodeJS with asdf

```sh
brew install asdf
asdf plugin add nodejs
Expand All @@ -79,34 +86,39 @@ asdf reshim

#### Fix path for Bash

Edit ~/.bash_profile to add path for asdf shims:
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:
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

See https://asdf-vm.com/guide/getting-started.html#_2-configure-asdf

### Clone Trusted Server and Configure Build
### 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)
- 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
Expand All @@ -124,11 +136,13 @@ 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)

Expand All @@ -141,13 +155,15 @@ 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
Expand All @@ -166,6 +182,7 @@ Request signing requires Fastly Config Store and Secret Store for key management
- 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
Expand All @@ -187,7 +204,6 @@ Once configured, the following endpoints are available:
- **`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)
Expand All @@ -196,9 +212,9 @@ Once configured, the following endpoints are available:

## 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=<base-url>&<original-query-params>&tstoken=<sig>` (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/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=<base-url>&<original-query-params>&tstoken=<sig>` (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
Expand All @@ -223,7 +239,12 @@ Once configured, the following endpoints are available:

- 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 partners can correlate impressions and clicks without direct third-party cookies.
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.
2 changes: 2 additions & 0 deletions crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ brotli = { workspace = true }
bytes = { workspace = true }
chacha20poly1305 = { workspace = true }
chrono = { workspace = true }
async-trait = { workspace = true }
config = { workspace = true }
cookie = { workspace = true }
derive_more = { workspace = true }
Expand Down Expand Up @@ -42,6 +43,7 @@ urlencoding = { workspace = true }
uuid = { workspace = true }
validator = { workspace = true }
ed25519-dalek = { workspace = true }
once_cell = { workspace = true }

[build-dependencies]
config = { workspace = true }
Expand Down
14 changes: 7 additions & 7 deletions crates/common/src/creative.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,12 +300,12 @@ pub fn rewrite_creative_html(markup: &str, settings: &Settings) -> String {
let mut rewriter = HtmlRewriter::new(
HtmlSettings {
element_content_handlers: vec![
// Inject tsjs-creative at the top of body once
// Inject unified tsjs bundle at the top of body once
element!("body", {
let injected = injected_ts_creative.clone();
move |el| {
if !injected.get() {
let script_tag = tsjs::creative_script_tag();
let script_tag = tsjs::unified_script_tag();
el.prepend(&script_tag, ContentType::Html);
injected.set(true);
}
Expand Down Expand Up @@ -490,20 +490,20 @@ mod tests {
let html = r#"<html><body><p>hello</p></body></html>"#;
let out = rewrite_creative_html(html, &settings);
assert!(
out.contains("/static/tsjs=tsjs-creative.min.js"),
"expected tsjs-creative injection: {}",
out.contains("/static/tsjs=tsjs-unified.min.js"),
"expected unified tsjs injection: {}",
out
);
// Inject only once
assert_eq!(out.matches("/static/tsjs=tsjs-creative.min.js").count(), 1);
assert_eq!(out.matches("/static/tsjs=tsjs-unified.min.js").count(), 1);
}

#[test]
fn injects_tsjs_creative_once_with_multiple_bodies() {
fn injects_tsjs_unified_once_with_multiple_bodies() {
let settings = crate::test_support::tests::create_test_settings();
let html = r#"<html><body>one</body><body>two</body></html>"#;
let out = rewrite_creative_html(html, &settings);
assert_eq!(out.matches("/static/tsjs=tsjs-creative.min.js").count(), 1);
assert_eq!(out.matches("/static/tsjs=tsjs-unified.min.js").count(), 1);
}

#[test]
Expand Down
8 changes: 8 additions & 0 deletions crates/common/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ pub enum TrustedServerError {
#[display("Prebid error: {message}")]
Prebid { message: String },

/// Integration module error.
#[display("Integration error ({integration}): {message}")]
Integration {
integration: String,
message: String,
},

/// Proxy error.
#[display("Proxy error: {message}")]
Proxy { message: String },
Expand Down Expand Up @@ -91,6 +98,7 @@ impl IntoHttpResponse for TrustedServerError {
Self::InvalidUtf8 { .. } => StatusCode::BAD_REQUEST,
Self::KvStore { .. } => StatusCode::SERVICE_UNAVAILABLE,
Self::Prebid { .. } => StatusCode::BAD_GATEWAY,
Self::Integration { .. } => StatusCode::BAD_GATEWAY,
Self::Proxy { .. } => StatusCode::BAD_GATEWAY,
Self::SyntheticId { .. } => StatusCode::INTERNAL_SERVER_ERROR,
Self::Template { .. } => StatusCode::INTERNAL_SERVER_ERROR,
Expand Down
Loading
Loading