عنوان:

‫ NGINX برای توسعه‌دهندگان ASP.NET Core - قسمت دوم - راه‌اندازی اولین Reverse Proxy


نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۳/۲۸ ۰۸:۰۵
آدرس: www.dntips.ir
در ادامه این سری، وارد یکی از عملی‌ترین و مهم‌ترین بخش‌های دوره می‌شویم. در این بخش، قدم به قدم یک اپلیکیشن ASP.NET Core را روی یک سرور لینوکس قرار داده و NGINX را به عنوان یک واسطه قدرتمند در جلوی آن پیکربندی می‌کنیم.

بخش ۲: راه‌اندازی اولین Reverse Proxy برای ASP.NET Core
در این بخش، آستین‌ها را بالا می‌زنیم و به صورت عملی یک اپلیکیشن را برای محیط Production آماده می‌کنیم. هدف ما این است که کاربر با وارد کردن آدرس دامنه یا IP سرور شما در مرورگر، به اپلیکیشن ASP.NET Core شما متصل شود، در حالی که NGINX تمام درخواست‌ها را مدیریت می‌کند.

۱. آماده‌سازی اپلیکیشن ASP.NET Core
قبل از هر چیز، به یک اپلیکیشن برای استقرار نیاز داریم. می‌توانید از پروژه خودتان استفاده کنید یا یک پروژه جدید بسازید.

الف) انتشار (Publish) پروژه
ما نباید سورس کد را مستقیماً به سرور منتقل کنیم. به جای آن، باید پروژه را در حالت Release منتشر کنیم تا فایل‌های dll بهینه شده و تمام نیازمندی‌های آن در یک پوشه جمع شوند.
در پوشه اصلی پروژه خود، دستور زیر را در ترمینال اجرا کنید:
dotnet publish -c Release
این دستور، خروجی پروژه را در پوشه bin/Release/netX.X/publish ایجاد می‌کند. (X.X نسخه دات‌نت شماست). تمام محتویات این پوشه publish را باید به سرور لینوکس خود منتقل کنید.

ب) انتقال فایل‌ها به سرور
فایل‌های منتشر شده را در یک مسیر مشخص روی سرور خود قرار دهید. یک مسیر استاندارد برای این کار /var/www/ است. برای مثال، ما یک پوشه برای اپلیکیشن خود به نام my-aspnet-app می‌سازیم:
# در سرور لینوکس
sudo mkdir -p /var/www/my-aspnet-app
سپس فایل‌های داخل پوشه publish را به این مسیر منتقل کنید (می‌توانید از ابزارهایی مانند scp یا FileZilla استفاده کنید).

ج) پیکربندی اپلیکیشن برای کار با Reverse Proxy
این یک مرحله بسیار مهم است. وقتی NGINX درخواست را به Kestrel پاس می‌دهد، برخی اطلاعات کلیدی مانند IP واقعی کاربر یا پروتکل (HTTP/HTTPS) از بین می‌رود. باید به اپلیکیشن ASP.NET Core بگوییم که به هدرهایی که پراکسی (NGINX) اضافه می‌کند، اعتماد کند.
فایل Program.cs خود را باز کرده و کد زیر را به آن اضافه کنید:
// using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

// ... سرویس‌های دیگر

// اضافه کردن این بخش برای خواندن هدرهای Forward شده توسط NGINX
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = 
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});

var app = builder.Build();

// و اضافه کردن این خط قبل از سایر Middleware ها
app.UseForwardedHeaders();

// ... سایر Middleware ها مانند app.UseHttpsRedirection();
این کد باعث می‌شود اطلاعاتی که NGINX در هدرهای X-Forwarded-For و X-Forwarded-Proto قرار می‌دهد، به درستی توسط اپلیکیشن شما خوانده شود.

۲. اجرای اپلیکیشن با استفاده از systemd به عنوان یک سرویس
اگر اپلیکیشن را با دستور dotnet MyWebApp.dll در ترمینال اجرا کنید، با بستن ترمینال، اپلیکیشن هم متوقف می‌شود. برای اینکه اپلیکیشن شما به صورت دائمی در پس‌زمینه اجرا شود و در صورت بروز خطا یا ری‌استارت شدن سرور، به طور خودکار دوباره اجرا شود، باید آن را به یک سرویس لینوکس تبدیل کنیم. ابزار استاندارد برای این کار systemd است.
الف) ساخت فایل سرویس
یک فایل سرویس جدید در مسیر /etc/systemd/system/ ایجاد می‌کنیم:
sudo nano /etc/systemd/system/kestrel-my-aspnet-app.service

ب) قرار دادن محتوای زیر در فایل
محتوای زیر را در ویرایشگر nano کپی کرده و متناسب با پروژه خودتان ویرایش کنید.
[Unit]
Description=My ASP.NET Core application running on Kestrel

[Service]
WorkingDirectory=/var/www/my-aspnet-app
ExecStart=/usr/bin/dotnet /var/www/my-aspnet-app/YourApp.dll
Restart=always
# Restart service after 10 seconds if it crashes
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=my-aspnet-app
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_CONSOLE_OUTPUT=true

[Install]
WantedBy=multi-user.target
توضیح بخش‌های مهم:
  • WorkingDirectory: مسیر پوشه اپلیکیشن شما.
  • ExecStart: دستوری که برای اجرای اپلیکیشن لازم است. حتماً نام فایل dll خود را جایگزین YourApp.dll کنید.
  • Restart=always: باعث می‌شود سرویس همیشه بعد از توقف، مجدداً راه‌اندازی شود.
  • User=www-data: اجرای سرویس با کاربر www-data (کاربر استاندارد وب سرورها) امنیت را افزایش می‌دهد. باید مطمئن شوید این کاربر دسترسی لازم به پوشه اپلیکیشن را دارد (sudo chown -R www-data:www-data /var/www/my-aspnet-app).

ج) فعال‌سازی و اجرای سرویس
حالا به systemd می‌گوییم که سرویس جدید ما را بشناسد و آن را اجرا کند:
sudo systemctl enable kestrel-my-aspnet-app.service  # فعال‌سازی برای اجرای خودکار بعد از بوت
sudo systemctl start kestrel-my-aspnet-app.service   # اجرای سرویس برای اولین بار
sudo systemctl status kestrel-my-aspnet-app.service  # بررسی وضعیت سرویس
اگر وضعیت active (running) را مشاهده کردید، اپلیکیشن شما با موفقیت در پس‌زمینه در حال اجراست.

۳. پیکربندی NGINX به عنوان Reverse Proxy
اکنون که اپلیکیشن ما در حال اجراست، وقت آن است که NGINX را به عنوان درگاه ورودی آن تنظیم کنیم.

الف) ساخت فایل کانفیگ NGINX
یک فایل کانفیگ جدید در sites-available ایجاد می‌کنیم:
sudo nano /etc/nginx/sites-available/my-aspnet-app.conf

ب) قرار دادن محتوای زیر در فایل
این بلوک server، قلب تنظیمات Reverse Proxy ماست. آن را کپی کرده و آدرس دامنه یا IP خود را جایگزین کنید.
server {
    listen 80;
    server_name your_domain_or_ip; # مثلا my-app.com یا 192.168.1.100

    location / {
        proxy_pass         http://localhost:5000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        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;
    }
}
توضیح خطوط کلیدی:
  • listen 80: به NGINX می‌گوید به درخواست‌های ورودی روی پورت 80 (HTTP) گوش دهد.
  • server_name: مشخص می‌کند این تنظیمات برای کدام دامنه یا IP اعمال شود.
  • location /: این بلاک برای تمام درخواست‌هایی که به این دامنه می‌آیند (/) اعمال می‌شود.
  • proxy_pass http://localhost:5000: مهم‌ترین خط! این دستور به NGINX می‌گوید هر درخواستی که دریافت می‌کند را به آدرس http://localhost:5000 (جایی که Kestrel در حال اجراست) ارسال کند.
  • proxy_set_header: این خطوط، هدرهای اصلی درخواست را حفظ کرده و به اپلیکیشن ASP.NET Core شما ارسال می‌کنند تا اپلیکیشن از هویت واقعی کاربر و نوع درخواست مطلع باشد.

ج) فعال‌سازی سایت و راه‌اندازی مجدد NGINX
# ایجاد لینک برای فعال‌سازی
sudo ln -s /etc/nginx/sites-available/my-aspnet-app.conf /etc/nginx/sites-enabled/

# تست کردن صحت فایل کانفیگ
sudo nginx -t

# اگر تست موفق بود، NGINX را reload کنید
sudo systemctl reload nginx
اکنون اگر آدرس دامنه یا IP سرور خود را در مرورگر وارد کنید، باید اپلیکیشن ASP.NET Core خود را ببینید!

۴. عیب‌یابی اولیه
اگر با خطا مواجه شدید، نگران نباشید. رایج‌ترین خطا 502 Bad Gateway است. این خطا یعنی NGINX نتوانسته با اپلیکیشن شما (Kestrel) ارتباط برقرار کند.
چک‌لیست رفع خطای 502:
  • آیا سرویس Kestrel در حال اجراست؟ sudo systemctl status kestrel-my-aspnet-app.service را اجرا کنید. اگر سرویس failed شده بود، با دستور journalctl -fu kestrel-my-aspnet-app.service لاگ‌های آن را برای پیدا کردن علت خطا بررسی کنید.
  • آیا آدرس proxy_pass درست است؟ مطمئن شوید پورتی که در proxy_pass (مثلاً 5000) نوشته‌اید، با پورتی که Kestrel روی آن اجرا می‌شود، یکسان است.
  • آیا فایروال مانع ارتباط شده است؟ اگر فایروال ufw فعال است، مطمئن شوید پورت 80 باز است: sudo ufw allow 'Nginx HTTP'.
  • بررسی لاگ‌های NGINX: برای دیدن خطاهای NGINX، از دستور زیر استفاده کنید: sudo tail -f /var/log/nginx/error.log

تبریک می‌گوییم! شما با موفقیت یک اپلیکیشن ASP.NET Core را با ساختاری استاندارد و حرفه‌ای در محیط لینوکس مستقر کردید. در بخش‌های بعدی، این ساختار را با افزودن HTTPS، بهینه‌سازی و امنیت، کامل‌تر خواهیم کرد.

نظرات

  • وحید نصیری در ۱۴۰۴/۰۳/۲۸ ۰۸:۳۳
    یک نکته‌ی تکمیلی: چگونه تشخیص دهیم که nginx امکان دسترسی و ارائه‌ی برنامه وب را بر روی پورت 5000 دارد؟

    برای اینکه مطمئن شوید Nginx به درستی برای ارائه یک سایت ASP.NET که روی پورت 5000 اجرا می‌شود تنظیم شده است، باید چند بخش کلیدی را بررسی کنید. این فرآیند شامل بررسی فایل‌های پیکربندی Nginx و اطمینان از عدم وجود مسدودیت در سطح سیستم‌عامل است.
    در اینجا مراحل اصلی برای تشخیص این موضوع آمده است:

    ۱. بررسی فایل پیکربندی Nginx
    مهم‌ترین بخش، بررسی فایل nginx.conf و فایل‌های پیکربندی مربوط به سایت شماست. این فایل‌ها معمولاً در مسیرهای زیر یافت می‌شوند:
    • /etc/nginx/nginx.conf (فایل اصلی)
    • /etc/nginx/conf.d/ (فایل‌های پیکربندی اضافی)
    • /etc/nginx/sites-available/ و /etc/nginx/sites-enabled/ (پیکربندی‌های مربوط به هر سایت)
    در فایل پیکربندی مربوط به سایت ASP.NET خود، به دنبال بلوک server بگردید. درون این بلوک، باید یک بخش location وجود داشته باشد که درخواست‌ها را به برنامه ASP.NET شما هدایت (proxy) می‌کند. این کار با استفاده از دستور proxy_pass انجام می‌شود.
    نمونه پیکربندی صحیح:
    server {
        listen 80;
        server_name your_domain.com www.your_domain.com;
    
        location / {
            proxy_pass http://localhost:5000;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
            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;
        }
    }
    نکته کلیدی: مطمئن شوید که مقدار proxy_pass به آدرس و پورتی که برنامه ASP.NET شما روی آن در حال اجراست، یعنی http://localhost:5000 یا http://127.0.0.1:5000، اشاره می‌کند.
    پس از هر تغییر در فایل پیکربندی، حتماً آن را تست کرده و Nginx را مجدداً بارگذاری کنید:
    sudo nginx -t
    sudo systemctl reload nginx

    ۲. بررسی وضعیت برنامه ASP.NET
    اطمینان حاصل کنید که برنامه ASP.NET شما در حال اجراست و روی پورت 5000 گوش می‌دهد. می‌توانید از دستور netstat یا ss برای این کار استفاده کنید:
    sudo netstat -tulpn | grep :5000
    یا
    sudo ss -tulpn | grep :5000
    خروجی این دستور باید نشان دهد که یک فرآیند (معمولاً با نام dotnet) در حال گوش دادن روی پورت 5000 است. اگر خروجی‌ای دریافت نکردید، یعنی برنامه شما یا اجرا نشده یا روی پورت دیگری تنظیم شده است.

    همچنین وضعیت برنامه ASP.NET خود را بررسی کنید: مطمئن شوید که سرویس dotnet شما فعال و در حال اجراست.
    systemctl status your-aspnet-app.service

    ۳. بررسی فایروال و SELinux
    گاهی اوقات فایروال سیستم‌عامل یا سیاست‌های امنیتی مانند SELinux (در سیستم‌های مبتنی بر Red Hat) می‌توانند مانع از برقراری ارتباط بین Nginx و برنامه شما شوند.

    فایروال (UFW در اوبونتو/دبیان):
    • مطمئن شوید که پورت 5000 مسدود نیست. اگرچه ارتباط بین Nginx و برنامه شما به صورت داخلی (localhost) است و معمولاً تحت تأثیر فایروال قرار نمی‌گیرد، اما بررسی آن ضرری ندارد. مهم‌تر از آن، باید مطمئن شوید که پورت‌های 80 (HTTP) و 443 (HTTPS) برای دسترسی عمومی باز هستند:
    sudo ufw status
    • اگر فایروال فعال است، اطمینان حاصل کنید که Nginx مجاز است:
    sudo ufw allow 'Nginx Full'

    SELinux (در CentOS/RHEL):
    • SELinux ممکن است به طور پیش‌فرض به وب‌سرورها اجازه ندهد به پورت‌های غیر استاندارد متصل شوند. برای بررسی اینکه آیا SELinux مانع ایجاد کرده است، می‌توانید به طور موقت آن را در حالت "permissive" قرار دهید:
    sudo setenforce 0
    • سپس سایت خود را تست کنید. اگر مشکل حل شد، باید یک سیاست دائمی برای اجازه دادن به Nginx برای اتصال به شبکه اضافه کنید:
    sudo setsebool -P httpd_can_network_connect 1
    • در نهایت، SELinux را دوباره به حالت "enforcing" بازگردانید:
    sudo setenforce 1

    ۴. بررسی لاگ‌های خطا
    لاگ‌های Nginx اطلاعات بسیار مفیدی در مورد خطاها ارائه می‌دهند. اگر در اتصال به برنامه خود مشکل دارید، این فایل‌ها را بررسی کنید:
    • لاگ خطا:/var/log/nginx/error.log
    • لاگ دسترسی:/var/log/nginx/access.log
    به دنبال پیام‌های خطایی مانند (111: Connection refused) یا (13: Permission denied) بگردید. این خطاها به ترتیب نشان می‌دهند که یا برنامه‌ای روی پورت 5000 در حال اجرا نیست یا یک مشکل دسترسی (مانند SELinux) وجود دارد.

    با دنبال کردن این چهار مرحله، می‌توانید به طور کامل تشخیص دهید که آیا Nginx به درستی به برنامه ASP.NET شما در پورت 5000 دسترسی دارد یا خیر و در صورت وجود مشکل، آن را برطرف کنید.
  • وحید نصیری در ۱۴۰۴/۰۳/۲۸ ۰۹:۵۶
    یک نکته‌ تکمیلی: توضیح کامل تنظیمات کاربری سرویس مانند User=www-data و chown برای کاربران ویندوزی

    تصور کنید سرور شما یک هتل بزرگ و امن است.

    بخش اول: چرا اصلاً از یک کاربر دیگر استفاده می‌کنیم؟ (اصل حداقل دسترسی)
    در ویندوز، شما به عنوان یک کاربر با حساب کاربری خودتان لاگین می‌کنید. اگر این حساب از نوع Administrator باشد، شما کلید تمام اتاق‌ها، دفاتر، و گاوصندوق اصلی هتل را در اختیار دارید.
    حالا فرض کنید اپلیکیشن وب شما (مثلاً سایت رزرو هتل) یک ربات خدمتکار است که باید در هتل کار کند. شما دو انتخاب دارید:
    • روش ناامن (معادل اجرای برنامه به عنوان Administrator): شما کلید اصلی مدیر هتل را به ربات خدمتکار می‌دهید. ربات کارش را انجام می‌دهد، اما اگر یک هکر این ربات را هک کند، او حالا کلید اصلی کل هتل را به دست آورده است! می‌تواند وارد اتاق مدیریت شود، سیستم امنیتی را دستکاری کند و به گاوصندوق دسترسی پیدا کند. این یک فاجعه امنیتی است.
    • روش امن (استفاده از User=www-data): شما یک کارت دسترسی محدود مخصوص برای ربات خدمتکار تعریف می‌کنید. این کارت فقط درب اتاق‌های مهمانان (پوشه اپلیکیشن شما) و انبار ملحفه‌ها (فایل‌های مورد نیاز اپلیکیشن) را باز می‌کند. این کارت به هیچ وجه درب اتاق مدیریت (فایل‌های سیستمی لینوکس) یا گاوصندوق (اطلاعات کاربران دیگر) را باز نمی‌کند.

    User=www-data دقیقاً همین کارت دسترسی محدود است.
    • root در لینوکس: معادل کاربر Administrator در ویندوز است. او قدرت مطلق را در کل سیستم دارد.
    • www-data در لینوکس: یک کاربر ویژه و غیرانسانی است که به طور خودکار هنگام نصب وب‌سرورهایی مانند NGINX ایجاد می‌شود. هدف از خلقت این کاربر، فقط و فقط اجرای فرآیندهای مربوط به وب با حداقل دسترسی ممکن است. این کاربر حتی نمی‌تواند به سیستم لاگین کند.

    نتیجه‌گیری امنیتی: اگر یک هکر در اپلیکیشن ASP.NET Core شما یک حفره امنیتی پیدا کند، او فقط به سطح دسترسی کاربری که اپلیکیشن را اجرا می‌کند، می‌رسد.
    • اگر اپلیکیشن با root اجرا شود، هکر کل سرور را به دست آورده است.
    • اگر اپلیکیشن با www-data اجرا شود، هکر فقط در محدوده همان پوشه اپلیکیشن شما (/var/www/my-aspnet-app) گیر می‌افتد و نمی‌تواند به بخش‌های حیاتی سیستم آسیب بزند. به این مفهوم "اصل حداقل دسترسی" (Principle of Least Privilege) می‌گویند.

    بخش دوم: chown چیست و چرا به آن نیاز داریم؟ (انتقال مالکیت)
    حالا که با کاربر www-data آشنا شدیم، به یک مشکل برمی‌خوریم.
    شما به عنوان کاربر اصلی خودتان (مثلاً کاربری به نام ubuntu که یک حساب مدیرگونه دارد) به سرور متصل شده‌اید. وقتی فایل‌های اپلیکیشن را در پوشه /var/www/my-aspnet-app کپی می‌کنید، سیستم‌عامل لینوکس می‌گوید: "مالک این فایل‌ها، کاربر ubuntu است."
    حالا ما در فایل سرویس (systemd) به سیستم دستور داده‌ایم: "لطفاً اپلیکیشن ما را با کاربر www-data اجرا کن."
    چه اتفاقی می‌افتد؟ کاربر www-data (ربات خدمتکار) سعی می‌کند اپلیکیشن را اجرا کند، اما سیستم‌عامل جلوی او را می‌گیرد و می‌گوید: "اجازه نداری! تو مالک این فایل‌ها نیستی. مالک آن‌ها آقای ubuntu است!" و اپلیکیشن شما با خطای "Access Denied" اجرا نمی‌شود.

    راه حل: دستور chown (Change Owner)
    ما باید به صورت دستی، مالکیت پوشه اپلیکیشن و تمام محتویات آن را از کاربر ubuntu به کاربر www-data منتقل کنیم. با این کار، کلیدهای بخش خانه‌داری را به سرپرست خدمه تحویل می‌دهید. بیایید دستور را کالبدشکافی کنیم:
    sudo chown -R www-data:www-data /var/www/my-aspnet-app
    • sudo: معادل "Run as Administrator" در ویندوز. یعنی "این دستور را با قدرت مدیریتی اجرا کن". ما برای تغییر مالکیت فایل‌ها به این سطح دسترسی نیاز داریم.
    • chown: خود دستور است، مخفف "change owner" (تغییر مالک).
    • -R: بسیار بسیار مهم! این آپشن مخفف "Recursive" (بازگشتی) است. یعنی "این تغییر مالکیت را نه فقط روی خود پوشه my-aspnet-app، بلکه روی تمام فایل‌ها و زیرپوشه‌های داخل آن نیز اعمال کن."
    • www-data:www-data: این بخش فرمت user:group را دارد.
    • www-data قبل از دو نقطه (:): مالک کاربر جدید را مشخص می‌کند.
    • www-data بعد از دو نقطه (:): مالک گروه جدید را مشخص می‌کند. (در لینوکس هر فایل یک کاربر مالک و یک گروه مالک دارد).
    • /var/www/my-aspnet-app: مسیری که می‌خواهیم مالکیت آن را تغییر دهیم.

    خلاصه فرآیند در سه گام
    • هدف امنیتی: ما نمی‌خواهیم اپلیکیشن‌مان با کاربر قدرتمند root اجرا شود. پس یک کاربر ضعیف و محدود به نام www-data را انتخاب می‌کنیم.
    • دستور به سیستم: در فایل سرویس systemd با خط User=www-data به لینوکس می‌گوییم که اپلیکیشن را با این کاربر محدود اجرا کند.
    • اعطای مجوز: چون ما فایل‌ها را با کاربر دیگری ساخته‌ایم، با دستور chown مالکیت آن‌ها را به کاربر www-data می‌دهیم تا او اجازه داشته باشد فایل‌های اپلیکیشن را بخواند و اجرا کند.

    این سه گام، یک زیربنای امن و استاندارد برای اجرای هر نوع اپلیکیشن وبی روی سرور لینوکس است.

  • وحید نصیری در ۱۴۰۴/۰۳/۲۸ ۱۰:۱۱
    یک سؤال مهم: این کاربر www-data از کجا آمده؟

    پاسخ کوتاه این است: شما آن را نساخته‌اید! کاربر www-data به صورت خودکار و در پشت صحنه، زمانی که شما یک وب سرور مانند NGINX یا Apache را روی یک توزیع لینوکس مبتنی بر دبیان (مثل اوبونتو) نصب می‌کنید، ایجاد می‌شود. بیایید این فرآیند را برای کسی که از دنیای ویندوز آمده، با جزئیات بیشتری باز کنیم.

    ۱. از کجا می‌آید؟ جادوی پکیج منیجر (apt)
    در ویندوز، وقتی یک نرم‌افزار را نصب می‌کنید، یک فایل setup.exe را اجرا می‌کنید. این فایل نه تنها فایل‌های برنامه را کپی می‌کند، بلکه ممکن است تغییراتی در رجیستری، فایروال یا حتی گروه‌های کاربری ویندوز ایجاد کند. در لینوکس (اوبونتو)، ابزار apt همین کار را انجام می‌دهد. وقتی شما دستور زیر را اجرا می‌کنید:
    sudo apt install nginx
    apt فقط یک سری فایل را کپی نمی‌کند، بلکه یک اسکریپت نصب (Installation Script) را هم که توسط توسعه‌دهندگان NGINX آماده شده، اجرا می‌کند. این اسکریپت مجموعه‌ای از دستورات است که سرور شما را برای اجرای صحیح NGINX آماده می‌کند. یکی از مهم‌ترین کارهای این اسکریپت، همین مدیریت کاربر است:
    • بررسی می‌کند: آیا کاربری به نام www-data در سیستم وجود دارد؟
    • اگر وجود نداشت، آن را ایجاد می‌کند: اسکریپت به صورت خودکار دستور useradd را با تنظیمات خاصی اجرا کرده و کاربر www-data را می‌سازد.
    • گروه را هم ایجاد می‌کند: همزمان یک گروه به نام www-data هم می‌سازد و این کاربر را عضو این گروه می‌کند.

    چرا این کار را می‌کند؟
    چون توسعه‌دهندگان NGINX می‌دانند که اجرای وب سرور با کاربر root (مدیر کل) یک ریسک امنیتی بزرگ است. آن‌ها با ساختن خودکار این کاربر کم‌اختیار، یک پیکربندی "امن به صورت پیش‌فرض" (Secure by Default) را برای شما فراهم می‌کنند تا شما به عنوان کاربر نهایی، نگران این جزئیات امنیتی پایه نباشید.

    ۲. چگونه تنظیم شده؟ تفاوت "کاربر سیستمی" و "کاربر عادی"
    این مهم‌ترین بخش برای درک موضوع است. در لینوکس، ما دو نوع کلی کاربر داریم:

    الف) کاربر عادی (Normal User):
    • مثال: کاربری به نام ubuntu که شما با آن به سرور SSH می‌زنید یا کاربری که روی دسکتاپ لینوکس خود می‌سازید.
    • هدف: برای استفاده توسط انسان‌ها جهت لاگین کردن و کار با سیستم.
    • ویژگی‌ها:یک پوشه خانه در مسیر /home/ دارد (مثلاً /home/ubuntu).
    • یک پوسته ورود (Login Shell) مانند /bin/bash دارد که به او اجازه می‌دهد دستورات را در ترمینال وارد کند.
    • می‌تواند لاگین کند.

    ب) کاربر سیستمی (System User):
    • مثال:www-data, mysql, postgres, mail.
    • هدف: برای استفاده توسط سرویس‌ها و برنامه‌ها جهت اجرای فرآیندها در پس‌زمینه.
    • ویژگی‌ها:معمولاً پوشه خانه‌ای در /home/ ندارد. (پوشه خانه www-data معمولاً /var/www است).
    • پوسته ورود (Login Shell) آن روی گزینه‌ای مانند /usr/sbin/nologin یا /bin/false تنظیم شده است.
    • نمی‌تواند لاگین کند. این یک ویژگی امنیتی کلیدی است! هیچ انسانی نمی‌تواند به عنوان کاربر www-data وارد ترمینال شود.

    www-data یک کاربر سیستمی است که فقط برای این ساخته شده که فرآیند NGINX (و در سناریوی ما، فرآیند اپلیکیشن ASP.NET Core) با هویت او اجرا شود و نه بیشتر.

    ۳. چگونه می‌توانیم خودمان آن را ببینیم و بررسی کنیم؟
    شما می‌توانید مانند یک کارآگاه، وجود و تنظیمات این کاربر را در سیستم خود بررسی کنید.

    دستور اول: id (شناسنامه کاربر)
    ساده‌ترین راه برای دیدن اطلاعات یک کاربر، استفاده از دستور id است. در ترمینال خود وارد کنید:
    id www-data
    خروجی چیزی شبیه به این خواهد بود:
    uid=33(www-data) gid=33(www-data) groups=33(www-data)
    • uid=33(www-data): شناسه کاربری (User ID) این کاربر 33 است.
    • gid=33(www-data): شناسه گروه اصلی (Group ID) آن نیز 33 است.

    دستور دوم: بررسی فایل/etc/passwd(دفتر ثبت احوال کاربران)
    این فایل متنی، اطلاعات تمام کاربران سیستم را در خود نگه می‌دارد. برای دیدن خط مربوط به www-data از دستور grep استفاده کنید:
    grep www-data /etc/passwd
    خروجی یک خط مانند این خواهد بود:
    www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
    بیایید این خط را که با : از هم جدا شده، رمزگشایی کنیم:
    • www-data: نام کاربری.
    • x: پسورد به صورت رمزنگاری شده در فایل دیگری ذخیره شده.
    • 33: شناسه کاربری (UID).
    • 33: شناسه گروه (GID).
    • www-data: یک فیلد توضیحات (Comment).
    • /var/www: مسیر پوشه خانه این کاربر.
    • /usr/sbin/nologin: مهم‌ترین بخش! این فیلد، پوسته ورود (Login Shell) را مشخص می‌کند. nologin یعنی "این کاربر حق لاگین کردن به سیستم را ندارد". این مدرک قطعی است که نشان می‌دهد www-data یک کاربر سیستمی و محدود شده است.

    خلاصه:
    کاربر www-data یک هویت امن و از پیش ساخته شده است که توسط نصب کننده NGINX به سیستم شما اضافه می‌شود تا به شما کمک کند اپلیکیشن‌های خود را بدون اعطای دسترسی‌های بیش از حد و خطرناک، اجرا کنید.
  • وحید نصیری در ۱۴۰۴/۰۳/۲۸ ۱۰:۲۱
    توضیحات بیشتری در مورد دلیل افزودن تنظیمات زیر به یک برنامه‌ی ASP.NET Core که قرار است در لینوکس و توسط nginx هاست شود:
    builder.Services.Configure<ForwardedHeadersOptions>(options =>
    {
        options.ForwardedHeaders = 
            ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    });

    این قطعه کد، یکی از حیاتی‌ترین و در عین حال گیج‌کننده‌ترین بخش‌ها برای توسعه‌دهندگانی است که برای اولین بار اپلیکیشن خود را پشت یک Reverse Proxy قرار می‌دهند. بیایید آن را با یک مثال ساده و قدم به قدم باز کنیم.

    توضیح عمیق ForwardedHeadersOptions برای تازه‌کارها
    تصور کنید اپلیکیشن ASP.NET Core شما یک کارمند بسیار مهم است که در یک دفتر کار شیشه‌ای در طبقه آخر یک برج امنیتی کار می‌کند. کاربران عادی (کلاینت‌ها) اجازه ورود مستقیم به برج را ندارند.
    • کاربر (Client): یک مشتری که به برج مراجعه می‌کند.
    • NGINX (نگهبان و مسئول پذیرش لابی): یک نگهبان امنیتی بسیار دقیق در لابی برج.
    • اپلیکیشن شما (Kestrel): کارمند مهم در دفتر داخلی.

    مرحله ۱: مشکل - کارمند دچار "فراموشی هویتی" می‌شود
    یک مشتری به نام "آقای رضایی" از طریق "در ورودی VIP" (یعنی با پروتکل امن HTTPS) و از آدرس "خیابان اصلی، پلاک ۸" (یعنی از IP Address: 8.8.8.8) وارد برج می‌شود.
    نگهبان (NGINX) او را در لابی متوقف می‌کند. نگهبان خودش از طریق تلفن داخلی با کارمند شما (Kestrel) تماس می‌گیرد و پیام آقای رضایی را به او منتقل می‌کند.
    از دید کارمند شما چه اتفاقی افتاده؟
    • تماس از کجا گرفته شده؟ از تلفن داخلی نگهبان (یعنی از آدرس IP: 127.0.0.1 یا همان localhost). کارمند شما IP واقعی آقای رضایی را نمی‌بیند.
    • پروتکل ارتباطی چه بود؟ یک تماس تلفنی داخلی ساده (یعنی پروتکل HTTP). کارمند شما خبر ندارد که آقای رضایی از در VIP و امن (HTTPS) وارد شده است.

    این مشکل در دنیای واقعی چه عواقبی دارد؟
    • از دست دادن IP کاربر: اگر بخواهید در کد خود IP کاربر را لاگ کنید (HttpContext.Connection.RemoteIpAddress)، همیشه آدرس 127.0.0.1 را خواهید دید. این برای امنیت، آمار و موقعیت‌یابی یک فاجعه است.
    • تولید لینک‌های اشتباه: اگر اپلیکیشن شما بخواهد یک لینک یا یک ریدایرکت ایجاد کند (مثلاً بعد از لاگین)، چون فکر می‌کند پروتکل http است، لینکی مانند http://mysite.com/profile تولید می‌کند. این کار باعث خطاهای Mixed Content در مرورگر شده یا حتی می‌تواند باعث یک حلقه ریدایرکت بی‌نهایت بین NGINX و اپلیکیشن شما شود.

    مرحله ۲: راه حل - "یادداشت‌های گزارشگر" از سمت نگهبان (NGINX)
    نگهبان‌های حرفه‌ای (مثل NGINX) برای حل این مشکل آموزش دیده‌اند. نگهبان وقتی پیام آقای رضایی را به کارمند شما منتقل می‌کند، دو یادداشت کوچک به آن پیوست می‌کند:
    • یادداشت شماره ۱ (هدر X-Forwarded-For): "گزارشگر IP اصلی: این پیام در اصل از طرف آقای رضایی با IP 8.8.8.8 است."
    • یادداشت شماره ۲ (هدر X-Forwarded-Proto): "گزارشگر پروتکل: ایشان از در ورودی VIP (https) وارد شده‌اند."
    حالا کارمند شما اطلاعات واقعی را در قالب این یادداشت‌ها دریافت می‌کند.

    مرحله ۳: اصل ماجرا - "سیاست اعتماد" در اپلیکیشن (توضیح کد)
    حالا یک سوال امنیتی مهم پیش می‌آید: آیا کارمند شما باید به هر یادداشتی که به دستش می‌رسد اعتماد کند؟ شاید یک خرابکار از بیرون برج، یک پیام با یادداشت جعلی فرستاده باشد!
    به همین دلیل، ASP.NET Core به صورت پیش‌فرض این یادداشت‌ها (هدرها) را نادیده می‌گیرد.
    اینجاست که آن قطعه کد وارد عمل می‌شود. شما به عنوان مدیر شرکت (توسعه‌دهنده)، یک سیاست کاری رسمی ابلاغ می‌کنید:
    builder.Services.Configure
    این خط کد می‌گوید: "من می‌خواهم سیاست‌ها و گزینه‌های مربوط به هدرهای گزارشگر (Forwarded Headers) را پیکربندی کنم."
    options => { ... }
    اینجا داریم جزئیات سیاست را مشخص می‌کنیم.
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    این مهم‌ترین خط است و معنای آن این است:
    "به موجب این ابلاغیه، به تمام کارمندان (Middleware ها) اجازه داده می‌شود که به دو نوع یادداشت خاص که از طرف نگهبان‌های معتمد ما (پراکسی) می‌آید، اعتماد کامل داشته باشند:"
    • ForwardedHeaders.XForwardedFor: مجوز رسمی برای خواندن یادداشت "گزارشگر IP اصلی" و جایگزین کردن IP داخلی نگهبان با آن.
    • ForwardedHeaders.XForwardedProto: مجوز رسمی برای خواندن یادداشت "گزارشگر پروتکل" و در نظر گرفتن پروتکل اصلی کاربر.
    علامت | در اینجا یک عملگر "OR بیتی" است که در این زمینه، معنای ساده‌اش "و همچنین" است. یعنی "ما می‌خواهیم هم به این هدر و هم به آن هدر اعتماد کنیم."

    مرحله ۴: اجرای سیاست با app.UseForwardedHeaders()
    بعد از تعریف این سیاست، شما باید با دستور app.UseForwardedHeaders(); در فایل Program.cs، یک "مامور اجرایی" (Middleware) را فعال کنید. وظیفه این مامور این است که در ابتدای هر درخواست، این یادداشت‌ها (هدرها) را بخواند و اگر طبق سیاست شرکت مجاز بودند، اطلاعات HttpContext را قبل از اینکه به دست کنترلرها و کدهای شما برسد، اصلاح کند.

    نتیجه نهایی:
    پس از اعمال این تنظیمات، وقتی در کنترلر خود می‌نویسید:
    • var ip = HttpContext.Connection.RemoteIpAddress; -> مقدار آن 8.8.8.8 خواهد بود.
    • var scheme = HttpContext.Request.Scheme; -> مقدار آن https خواهد بود.

    هویت کاربر به درستی بازیابی شده و اپلیکیشن شما به درستی کار می‌کند. شما با آن قطعه کد، به اپلیکیشن خودتان اعلام کرده‌اید که "من می‌دانم که پشت یک پراکسی معتمد هستم، پس لطفاً به گزارش‌های آن اعتماد کن."