Skip to content

Refactor to use common http modules #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
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
47 changes: 47 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Security Audit

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
schedule:
- cron: "0 0 * * 0" # Weekly on Sunday at midnight

jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Cache Cargo dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}

- name: Retrieve Rust version
id: rust-version
run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT
shell: bash

- name: Set up Rust tool chain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: ${{ steps.rust-version.outputs.rust-version }}

- name: Install cargo-audit
run: cargo install cargo-audit

- name: Run security audit
run: cargo audit

- name: Check for known vulnerabilities in dependencies
uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
61 changes: 57 additions & 4 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ members = [
]

[profile.release]
debug = 1
debug = 1
3 changes: 3 additions & 0 deletions crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ futures = "0.3"
handlebars = "6.3.2"
hex = "0.4.3"
hmac = "0.12.1"
http = "1.3.1"
log = "0.4.20"
log-fastly = "0.10.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.91"
sha2 = "0.10.6"
tokio = { version = "1.43", features = ["sync", "macros", "io-util", "rt", "time"] }
thiserror = "1.0"
url = "2.4.1"
uuid = { version = "1.0", features = ["v4", "serde"] }
21 changes: 18 additions & 3 deletions crates/common/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
pub const SYNTHETIC_HEADER_FRESH: &str = "X-Synthetic-Fresh";
pub const SYNTHETIC_HEADER_TRUSTED_SERVER: &str = "X-Synthetic-Trusted-Server";
pub const SYNTHETIC_HEADER_PUB_USER_ID: &str = "X-Pub-User-ID";
use http::header::HeaderName;

pub const HEADER_SYNTHETIC_FRESH: HeaderName = HeaderName::from_static("x-synthetic-fresh");
pub const HEADER_SYNTHETIC_PUB_USER_ID: HeaderName = HeaderName::from_static("x-pub-user-id");
pub const HEADER_SYNTHETIC_TRUSTED_SERVER: HeaderName =
HeaderName::from_static("x-synthetic-trusted-server");
pub const HEADER_X_CONSENT_ADVERTISING: HeaderName =
HeaderName::from_static("x-consent-advertising");
pub const HEADER_X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for");
pub const HEADER_X_GEO_CITY: HeaderName = HeaderName::from_static("x-geo-city");
pub const HEADER_X_GEO_CONTINENT: HeaderName = HeaderName::from_static("x-geo-continent");
pub const HEADER_X_GEO_COORDINATES: HeaderName = HeaderName::from_static("x-geo-coordinates");
pub const HEADER_X_GEO_COUNTRY: HeaderName = HeaderName::from_static("x-geo-country");
pub const HEADER_X_GEO_INFO_AVAILABLE: HeaderName = HeaderName::from_static("x-geo-info-available");
pub const HEADER_X_GEO_METRO_CODE: HeaderName = HeaderName::from_static("x-geo-metro-code");
pub const HEADER_X_GEO_REGION: HeaderName = HeaderName::from_static("x-geo-region");
pub const HEADER_X_SUBJECT_ID: HeaderName = HeaderName::from_static("x-subject-id");
pub const HEADER_X_REQUEST_ID: HeaderName = HeaderName::from_static("x-request-id");
74 changes: 62 additions & 12 deletions crates/common/src/cookies.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use cookie::{Cookie, CookieJar};
use fastly::http::header;
use fastly::Request;
use http::header;

use crate::http_wrapper::RequestWrapper;
use crate::settings::Settings;

const COOKIE_MAX_AGE: i32 = 365 * 24 * 60 * 60; // 1 year

Expand All @@ -17,7 +19,7 @@ pub fn parse_cookies_to_jar(s: &str) -> CookieJar {
jar
}

pub fn handle_request_cookies(req: &Request) -> Option<CookieJar> {
pub fn handle_request_cookies<T: RequestWrapper>(req: &T) -> Option<CookieJar> {
match req.get_header(header::COOKIE) {
Some(header_value) => {
let header_value_str: &str = header_value.to_str().unwrap_or("");
Expand All @@ -31,17 +33,20 @@ pub fn handle_request_cookies(req: &Request) -> Option<CookieJar> {
}
}

pub fn create_synthetic_cookie(synthetic_id: &str) -> String {
pub fn create_synthetic_cookie(synthetic_id: &str, settings: &Settings) -> String {
format!(
"synthetic_id={}; Domain=.auburndao.com; Path=/; Secure; SameSite=Lax; Max-Age={}",
synthetic_id, COOKIE_MAX_AGE,
"synthetic_id={}; Domain={}; Path=/; Secure; SameSite=Lax; Max-Age={}",
synthetic_id, settings.server.cookie_domain, COOKIE_MAX_AGE,
)
}

#[cfg(test)]
mod tests {
use super::*;

use crate::http_wrapper::tests::HttpRequestWrapper;
use http::request;

#[test]
fn test_parse_cookies_to_jar() {
let header_value = "c1=v1; c2=v2";
Expand Down Expand Up @@ -79,7 +84,11 @@ mod tests {

#[test]
fn test_handle_request_cookies() {
let req = Request::get("http://example.com").with_header(header::COOKIE, "c1=v1;c2=v2");
let builder = request::Builder::new()
.method("GET")
.uri("http://example.com")
.header(header::COOKIE, "c1=v1; c2=v2");
let req = HttpRequestWrapper::new(builder);
let jar = handle_request_cookies(&req).unwrap();

assert!(jar.iter().count() == 2);
Expand All @@ -89,34 +98,75 @@ mod tests {

#[test]
fn test_handle_request_cookies_with_empty_cookie() {
let req = Request::get("http://example.com").with_header(header::COOKIE, "");
let builder = request::Builder::new()
.method("GET")
.uri("http://example.com")
.header(header::COOKIE, "");
let req = HttpRequestWrapper::new(builder);
let jar = handle_request_cookies(&req).unwrap();

assert!(jar.iter().count() == 0);
}

#[test]
fn test_handle_request_cookies_no_cookie_header() {
let req: Request = Request::get("https://example.com");
let builder = request::Builder::new()
.method("GET")
.uri("http://example.com");
let req = HttpRequestWrapper::new(builder);
let jar = handle_request_cookies(&req);

assert!(jar.is_none());
}

#[test]
fn test_handle_request_cookies_invalid_cookie_header() {
let req = Request::get("http://example.com").with_header(header::COOKIE, "invalid");
let builder = request::Builder::new()
.method("GET")
.uri("http://example.com")
.header(header::COOKIE, "invalid");
let req = HttpRequestWrapper::new(builder);
let jar = handle_request_cookies(&req).unwrap();

assert!(jar.iter().count() == 0);
}

#[test]
fn test_create_synthetic_cookie() {
let result = create_synthetic_cookie("12345");
// Create a test settings
let settings_toml = r#"
[server]
domain = "example.com"
cookie_domain = ".example.com"

[ad_server]
ad_partner_url = "test"
sync_url = "test"

[prebid]
server_url = "test"

[synthetic]
counter_store = "test"
opid_store = "test"
secret_key = "test-key"
template = "test"
"#;

let settings: Settings = config::Config::builder()
.add_source(config::File::from_str(
settings_toml,
config::FileFormat::Toml,
))
.build()
.unwrap()
.try_deserialize()
.unwrap();

let result = create_synthetic_cookie("12345", &settings);
assert_eq!(
result,
"synthetic_id=12345; Domain=.auburndao.com; Path=/; Secure; SameSite=Lax; Max-Age=31536000"
"synthetic_id=12345; Domain=.example.com; Path=/; Secure; SameSite=Lax; Max-Age=31536000"
);
}
}
33 changes: 33 additions & 0 deletions crates/common/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use thiserror::Error;

#[derive(Error, Debug)]
pub enum TrustedServerError {
#[error("Configuration error: {0}")]
Config(#[from] config::ConfigError),

#[error("Template rendering error: {0}")]
Template(#[from] handlebars::RenderError),

#[error("Invalid UTF-8: {0}")]
Utf8(#[from] std::str::Utf8Error),

#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),

#[error("HTTP error: {0}")]
Http(String),

#[error("KV store error: {0}")]
KvStore(String),

#[error("Invalid request: {0}")]
InvalidRequest(String),

#[error("Security error: {0}")]
Security(String),

#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}

pub type Result<T> = std::result::Result<T, TrustedServerError>;
Loading
Loading