diff --git a/.env-example b/.env-example new file mode 100644 index 0000000..0f88b49 --- /dev/null +++ b/.env-example @@ -0,0 +1,4 @@ +MONGO_INITDB_DATABASE= +MONGO_INITDB_ROOT_USERNAME= +MONGO_INITDB_ROOT_PASSWORD= +VIRTUAL_HOST= diff --git a/.github/workflows/dependabot_automerge.yml b/.github/workflows/dependabot_automerge.yml index 235549d..e9d4f95 100644 --- a/.github/workflows/dependabot_automerge.yml +++ b/.github/workflows/dependabot_automerge.yml @@ -1,26 +1,25 @@ -name: Auto Merge Dependabot PRs +name: Dependabot auto-merge -on: - pull_request: - types: - - opened - - synchronize - - reopened +on: pull_request + +permissions: + contents: write + pull-requests: write jobs: - auto-merge: - name: Auto-merge dependabot PRs + dependabot: runs-on: ubuntu-latest - if: github.actor == 'dependabot[bot]' + if: github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == 'containerscrew/iproxy' steps: - - name: Checkout code - uses: actions/checkout@v4 + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v2.2.0 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" - - name: Auto-merge PR if it bumps the clap dependency - uses: pascalgn/automerge-action@v0.15.4 + - name: Enable auto-merge for Dependabot PRs +# if: contains(steps.metadata.outputs.dependency-names, 'my-dependency') && steps.metadata.outputs.update-type == 'version-update:semver-patch' + run: gh pr merge --auto --merge "$PR_URL" env: - GITHUB_TOKEN: ${{ secrets.ACTIONS_TOKEN }} - with: - merge-method: squash - #commit-message: "Auto-merged PR" - #title: "^Bump clap from .*$" + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/Dockerfile b/Dockerfile index d70b0d7..5e1aa0e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,4 +10,4 @@ WORKDIR /app COPY --from=build /app/target/release/iproxy /app/iproxy COPY config.toml /app/config.toml EXPOSE 8000 -CMD ["./iproxy"] \ No newline at end of file +CMD ["./iproxy"] diff --git a/README.md b/README.md index 7f9b7c5..186cf86 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,8 @@ Example, from [compose.yml](compose.yml): Now, launch all the stack: ```bash -docker network create iproxy +cp .env-example .env +# EDIT .env file as you need!!!!! make compose-up-build ``` diff --git a/compose.yml b/compose.yml index bac4292..6b50c97 100644 --- a/compose.yml +++ b/compose.yml @@ -1,4 +1,17 @@ services: + nginx-proxy: + image: docker.io/nginx:1.27.2-alpine + container_name: nginx-proxy + restart: unless-stopped + ports: + - 80:80 + networks: + - iproxy + environment: + - VIRTUAL_HOST=/run/secrets/VIRTUAL_HOST + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf:ro + iproxy: build: context: . @@ -7,38 +20,31 @@ services: container_name: iproxy networks: - iproxy - ports: - - 8000:8000 + mongodb: - image: docker.io/mongo:latest # TODO: do not use latest, use a specific version + image: docker.io/mongo:latest container_name: mongo networks: - iproxy restart: unless-stopped - ports: - - 27017:27017 environment: - - MONGO_INITDB_ROOT_USERNAME=admin - - MONGO_INITDB_ROOT_PASSWORD=admin - - MONGO_INITDB_DATABASE=iproxy + - MONGO_INITDB_ROOT_USERNAME=/run/secrets/MONGO_INITDB_ROOT_USERNAME + - MONGO_INITDB_ROOT_PASSWORD=/run/secrets/MONGO_INITDB_ROOT_PASSWORD + - MONGO_INITDB_DATABASE=/run/secrets/MONGO_INITDB_DATABASE volumes: - /mnt/ssd/iproxy:/data/db -# mongodb_exporter: -# image: docker.io/percona/mongodb_exporter:latest -# container_name: mongodb_exporter -# networks: -# - iproxy -# restart: unless-stopped -# ports: -# - 9216:9216 -# environment: -# - MONGODB_URI=mongodb://admin:admin@mongodb:27017 -# depends_on: -# - mongodb - -# docker network create iproxy networks: iproxy: driver: bridge - external: true + external: false + +secrets: + MONGO_INITDB_DATABASE: + file: .env + MONGO_INITDB_ROOT_USERNAME: + file: .env + MONGO_INITDB_ROOT_PASSWORD: + file: .env + VIRTUAL_HOST: + file: .env diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..5b5a3f8 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,77 @@ +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log notice; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + # Hide Nginx version information + server_tokens off; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + # Set timeouts to mitigate slow client attacks + client_body_timeout 12s; + client_header_timeout 12s; + keepalive_timeout 15s; + send_timeout 10s; + + # Limit request sizes to prevent buffer overflow attacks + client_max_body_size 10M; + client_body_buffer_size 1k; + client_header_buffer_size 1k; + large_client_header_buffers 2 1k; + + # Define a rate limiting zone named 'mylimit' with a size of 10MB + # Limit each IP to 10 requests per second + limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s; + + # Security headers to mitigate common attacks + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Content-Security-Policy "default-src 'self';" always; + + server { + listen 80; + server_name api.${VIRTUAL_HOST}; + + # Restrict allowed HTTP methods + if ($request_method !~ ^(GET|HEAD|POST)$ ) { + return 405; + } + + location / { + limit_req zone=mylimit burst=20 nodelay; + limit_except GET { deny all; } + + proxy_pass http://iproxy:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Define a custom error page for rate-limited responses + error_page 429 /custom_429.html; + location = /custom_429.html { + internal; + root /usr/share/nginx/html; + } + } +} diff --git a/src/config.rs b/src/config.rs index feff2df..6185463 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,7 +13,7 @@ pub struct ServerConfig { pub struct LoggingConfig { pub(crate) log_level: String, pub(crate) log_type: String, -} +} #[derive(Deserialize)] pub struct DatabaseConfig { diff --git a/src/logger.rs b/src/logger.rs index 71f7872..0f3c4d9 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,4 +1,4 @@ -use tracing_subscriber::fmt::{format::FmtSpan}; +use tracing_subscriber::fmt::format::FmtSpan; pub fn setup_logger(log_level: String, format: String) { let log_level = match log_level.as_str() { @@ -22,6 +22,6 @@ pub fn setup_logger(log_level: String, format: String) { match format.as_str() { "json" => base_subscriber.json().init(), "text" => base_subscriber.init(), - _ => base_subscriber.init(), + _ => base_subscriber.init(), }; } diff --git a/src/main.rs b/src/main.rs index 286ce84..27a3740 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,10 +4,10 @@ use crate::db::Db; use crate::handlers::handler_404; use crate::logger::setup_logger; use crate::router::create_router; +use axum::http::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE}; +use axum::http::{HeaderValue, Method}; use std::net::SocketAddr; use std::sync::Arc; -use axum::http::{HeaderValue, Method}; -use axum::http::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE}; use tower_http::cors::CorsLayer; use tracing::info; @@ -55,7 +55,8 @@ async fn main() -> Result<(), Box> { let app = create_router(Arc::new(AppState { db: db.clone(), use_proxy: config.server.use_proxy, - })).layer(cors); + })) + .layer(cors); let app = app.fallback(handler_404);