mirror of
https://github.com/NetworkCats/rustyip.git
synced 2026-11-13 08:15:53 +00:00
feat: add security headers, caching headers, and static file versioning
This commit is contained in:
Generated
+49
-36
@@ -215,6 +215,16 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "change-detection"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "159fa412eae48a1d94d0b9ecdb85c97ce56eb2a347c62394d3fdbf221adabc1a"
|
||||
dependencies = [
|
||||
"path-matchers",
|
||||
"path-slash 0.1.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.57"
|
||||
@@ -313,12 +323,6 @@ version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.32"
|
||||
@@ -364,6 +368,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.4.0"
|
||||
@@ -397,12 +407,6 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-range-header"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.10.1"
|
||||
@@ -760,6 +764,27 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||
|
||||
[[package]]
|
||||
name = "path-matchers"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36cd9b72a47679ec193a5f0229d9ab686b7bd45e1fbc59ccf953c9f3d83f7b2b"
|
||||
dependencies = [
|
||||
"glob",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "path-slash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "498a099351efa4becc6a19c72aa9270598e8fd274ca47052e37455241c88b696"
|
||||
|
||||
[[package]]
|
||||
name = "path-slash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
@@ -1070,9 +1095,9 @@ dependencies = [
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"static-files",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
@@ -1243,6 +1268,17 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "static-files"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "493af46ab6e8d5409eb02ced7af4bbc97643de491df7b4afcd1cd8fdcc91dc82"
|
||||
dependencies = [
|
||||
"change-detection",
|
||||
"mime_guess",
|
||||
"path-slash 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
@@ -1391,19 +1427,6 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.3"
|
||||
@@ -1428,24 +1451,14 @@ checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"http-range-header",
|
||||
"httpdate",
|
||||
"iri-string",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+4
-1
@@ -14,7 +14,10 @@ arc-swap = "1"
|
||||
reqwest = { version = "0.13", default-features = false, features = ["rustls"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
tower-http = { version = "0.6", features = ["fs"] }
|
||||
static-files = "0.3.1"
|
||||
|
||||
[build-dependencies]
|
||||
static-files = "0.3.1"
|
||||
|
||||
[dev-dependencies]
|
||||
http-body-util = "0.1"
|
||||
|
||||
+1
-3
@@ -4,7 +4,7 @@ FROM rust:1.93.1-alpine3.23 AS builder
|
||||
RUN apk add --no-cache musl-dev
|
||||
|
||||
WORKDIR /build
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
COPY Cargo.toml Cargo.lock build.rs ./
|
||||
COPY src ./src
|
||||
COPY templates ./templates
|
||||
COPY static ./static
|
||||
@@ -19,8 +19,6 @@ RUN apk add --no-cache ca-certificates \
|
||||
&& adduser -S -u 1001 -G rustyip rustyip
|
||||
|
||||
COPY --from=builder /build/target/release/rustyip /usr/local/bin/rustyip
|
||||
COPY --from=builder /build/templates /app/templates
|
||||
COPY --from=builder /build/static /app/static
|
||||
|
||||
WORKDIR /app
|
||||
RUN chown -R rustyip:rustyip /app
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
use static_files::resource_dir;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
resource_dir("./static").build()
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use crate::cli_detect::is_cli_user_agent;
|
||||
use crate::db::{self, SharedDb};
|
||||
use crate::error::AppError;
|
||||
use crate::models::{IpInfo, get_en_name};
|
||||
use crate::static_assets;
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct IpQuery {
|
||||
@@ -28,6 +29,7 @@ struct IndexTemplate {
|
||||
ip: String,
|
||||
query: String,
|
||||
site_domain: Arc<str>,
|
||||
css_version: u64,
|
||||
asn: String,
|
||||
org: String,
|
||||
country: String,
|
||||
@@ -125,6 +127,7 @@ pub async fn root(
|
||||
ip: info.ip.clone(),
|
||||
query: query.ip.unwrap_or_default(),
|
||||
site_domain: state.site_domain.clone(),
|
||||
css_version: static_assets::asset_version("style.css"),
|
||||
asn: format_asn(&info),
|
||||
org: format_org(&info).to_owned(),
|
||||
country: format_country(&info).to_owned(),
|
||||
|
||||
@@ -3,6 +3,8 @@ pub mod config;
|
||||
pub mod db;
|
||||
pub mod error;
|
||||
pub mod handlers;
|
||||
pub mod middleware;
|
||||
pub mod models;
|
||||
pub mod routes;
|
||||
pub mod static_assets;
|
||||
pub mod updater;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
use axum::extract::Request;
|
||||
use axum::http::header::{self, HeaderValue};
|
||||
use axum::middleware::Next;
|
||||
use axum::response::Response;
|
||||
|
||||
pub async fn security_headers(request: Request, next: Next) -> Response {
|
||||
let mut response = next.run(request).await;
|
||||
let headers = response.headers_mut();
|
||||
|
||||
headers.insert(
|
||||
header::X_CONTENT_TYPE_OPTIONS,
|
||||
HeaderValue::from_static("nosniff"),
|
||||
);
|
||||
headers.insert(header::X_FRAME_OPTIONS, HeaderValue::from_static("DENY"));
|
||||
headers.insert(
|
||||
header::REFERRER_POLICY,
|
||||
HeaderValue::from_static("strict-origin-when-cross-origin"),
|
||||
);
|
||||
headers.insert(
|
||||
header::STRICT_TRANSPORT_SECURITY,
|
||||
HeaderValue::from_static("max-age=63072000; includeSubDomains; preload"),
|
||||
);
|
||||
headers.insert(
|
||||
header::HeaderName::from_static("x-permitted-cross-domain-policies"),
|
||||
HeaderValue::from_static("none"),
|
||||
);
|
||||
headers.insert(
|
||||
header::HeaderName::from_static("permissions-policy"),
|
||||
HeaderValue::from_static(
|
||||
"camera=(), microphone=(), geolocation=(), interest-cohort=()",
|
||||
),
|
||||
);
|
||||
headers.insert(
|
||||
header::HeaderName::from_static("cross-origin-opener-policy"),
|
||||
HeaderValue::from_static("same-origin"),
|
||||
);
|
||||
headers.insert(
|
||||
header::HeaderName::from_static("cross-origin-resource-policy"),
|
||||
HeaderValue::from_static("same-origin"),
|
||||
);
|
||||
|
||||
response
|
||||
}
|
||||
+6
-3
@@ -1,8 +1,10 @@
|
||||
use axum::Router;
|
||||
use axum::middleware;
|
||||
use axum::routing::get;
|
||||
use tower_http::services::ServeDir;
|
||||
use axum::Router;
|
||||
|
||||
use crate::handlers::{self, AppState};
|
||||
use crate::middleware::security_headers;
|
||||
use crate::static_assets;
|
||||
|
||||
pub fn build_router(state: AppState) -> Router {
|
||||
Router::new()
|
||||
@@ -20,6 +22,7 @@ pub fn build_router(state: AppState) -> Router {
|
||||
.route("/vpn", get(handlers::vpn_handler))
|
||||
.route("/hosting", get(handlers::hosting_handler))
|
||||
.route("/tor", get(handlers::tor_handler))
|
||||
.nest_service("/static", ServeDir::new("static"))
|
||||
.route("/static/{*path}", get(static_assets::serve_static))
|
||||
.layer(middleware::from_fn(security_headers))
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use axum::extract::Path;
|
||||
use axum::http::header::{self, HeaderValue};
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
|
||||
use static_files::Resource;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
|
||||
|
||||
static ASSETS: LazyLock<HashMap<&'static str, Resource>> = LazyLock::new(generate);
|
||||
|
||||
pub fn asset_version(filename: &str) -> u64 {
|
||||
ASSETS
|
||||
.get(filename)
|
||||
.map(|r| r.modified)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
pub async fn serve_static(Path(path): Path<String>) -> Response {
|
||||
let Some(resource) = ASSETS.get(path.as_str()) else {
|
||||
return StatusCode::NOT_FOUND.into_response();
|
||||
};
|
||||
|
||||
(
|
||||
[
|
||||
(header::CONTENT_TYPE, HeaderValue::from_static(resource.mime_type)),
|
||||
(
|
||||
header::CACHE_CONTROL,
|
||||
HeaderValue::from_static("public, max-age=31536000, immutable"),
|
||||
),
|
||||
],
|
||||
resource.data,
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;600&display=swap">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/simple-line-icons/2.5.5/css/simple-line-icons.min.css">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<link rel="stylesheet" href="/static/style.css?v={{ css_version }}">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
|
||||
Reference in New Issue
Block a user