NGINX برای توسعهدهندگان ASP.NET Core - قسمت دوم - راهاندازی اولین Reverse Proxy
نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۳/۲۸ ۰۸:۰۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
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 استفاده کنید).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();X-Forwarded-For و X-Forwarded-Proto قرار میدهد، به درستی توسط اپلیکیشن شما خوانده شود.systemd به عنوان یک سرویسdotnet MyWebApp.dll در ترمینال اجرا کنید، با بستن ترمینال، اپلیکیشن هم متوقف میشود. برای اینکه اپلیکیشن شما به صورت دائمی در پسزمینه اجرا شود و در صورت بروز خطا یا ریاستارت شدن سرور، به طور خودکار دوباره اجرا شود، باید آن را به یک سرویس لینوکس تبدیل کنیم. ابزار استاندارد برای این کار systemd است./etc/systemd/system/ ایجاد میکنیم:sudo nano /etc/systemd/system/kestrel-my-aspnet-app.service
[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).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) را مشاهده کردید، اپلیکیشن شما با موفقیت در پسزمینه در حال اجراست.sudo nano /etc/nginx/sites-available/my-aspnet-app.conf
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 شما ارسال میکنند تا اپلیکیشن از هویت واقعی کاربر و نوع درخواست مطلع باشد.# ایجاد لینک برای فعالسازی sudo ln -s /etc/nginx/sites-available/my-aspnet-app.conf /etc/nginx/sites-enabled/ # تست کردن صحت فایل کانفیگ sudo nginx -t # اگر تست موفق بود، NGINX را reload کنید sudo systemctl reload nginx
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'.sudo tail -f /var/log/nginx/error.lognginx.conf و فایلهای پیکربندی مربوط به سایت شماست. این فایلها معمولاً در مسیرهای زیر یافت میشوند:/etc/nginx/nginx.conf (فایل اصلی)/etc/nginx/conf.d/ (فایلهای پیکربندی اضافی)/etc/nginx/sites-available/ و /etc/nginx/sites-enabled/ (پیکربندیهای مربوط به هر سایت)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، اشاره میکند.sudo nginx -t sudo systemctl reload nginx
netstat یا ss برای این کار استفاده کنید:sudo netstat -tulpn | grep :5000
sudo ss -tulpn | grep :5000
dotnet) در حال گوش دادن روی پورت 5000 است. اگر خروجیای دریافت نکردید، یعنی برنامه شما یا اجرا نشده یا روی پورت دیگری تنظیم شده است.dotnet شما فعال و در حال اجراست.systemctl status your-aspnet-app.service
sudo ufw status
sudo ufw allow 'Nginx Full'
sudo setenforce 0
sudo setsebool -P httpd_can_network_connect 1
sudo setenforce 1
/var/log/nginx/error.log/var/log/nginx/access.log(111: Connection refused) یا (13: Permission denied) بگردید. این خطاها به ترتیب نشان میدهند که یا برنامهای روی پورت 5000 در حال اجرا نیست یا یک مشکل دسترسی (مانند SELinux) وجود دارد.User=www-data و chown برای کاربران ویندوزیUser=www-data): شما یک کارت دسترسی محدود مخصوص برای ربات خدمتکار تعریف میکنید. این کارت فقط درب اتاقهای مهمانان (پوشه اپلیکیشن شما) و انبار ملحفهها (فایلهای مورد نیاز اپلیکیشن) را باز میکند. این کارت به هیچ وجه درب اتاق مدیریت (فایلهای سیستمی لینوکس) یا گاوصندوق (اطلاعات کاربران دیگر) را باز نمیکند.User=www-data دقیقاً همین کارت دسترسی محدود است.root در لینوکس: معادل کاربر Administrator در ویندوز است. او قدرت مطلق را در کل سیستم دارد.www-data در لینوکس: یک کاربر ویژه و غیرانسانی است که به طور خودکار هنگام نصب وبسرورهایی مانند NGINX ایجاد میشود. هدف از خلقت این کاربر، فقط و فقط اجرای فرآیندهای مربوط به وب با حداقل دسترسی ممکن است. این کاربر حتی نمیتواند به سیستم لاگین کند.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-appsudo: معادل "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 به صورت خودکار و در پشت صحنه، زمانی که شما یک وب سرور مانند NGINX یا Apache را روی یک توزیع لینوکس مبتنی بر دبیان (مثل اوبونتو) نصب میکنید، ایجاد میشود. بیایید این فرآیند را برای کسی که از دنیای ویندوز آمده، با جزئیات بیشتری باز کنیم.apt)setup.exe را اجرا میکنید. این فایل نه تنها فایلهای برنامه را کپی میکند، بلکه ممکن است تغییراتی در رجیستری، فایروال یا حتی گروههای کاربری ویندوز ایجاد کند. در لینوکس (اوبونتو)، ابزار apt همین کار را انجام میدهد. وقتی شما دستور زیر را اجرا میکنید:sudo apt install nginxapt فقط یک سری فایل را کپی نمیکند، بلکه یک اسکریپت نصب (Installation Script) را هم که توسط توسعهدهندگان NGINX آماده شده، اجرا میکند. این اسکریپت مجموعهای از دستورات است که سرور شما را برای اجرای صحیح NGINX آماده میکند. یکی از مهمترین کارهای این اسکریپت، همین مدیریت کاربر است:www-data در سیستم وجود دارد؟useradd را با تنظیمات خاصی اجرا کرده و کاربر www-data را میسازد.www-data هم میسازد و این کاربر را عضو این گروه میکند.ubuntu که شما با آن به سرور SSH میزنید یا کاربری که روی دسکتاپ لینوکس خود میسازید./home/ دارد (مثلاً /home/ubuntu)./bin/bash دارد که به او اجازه میدهد دستورات را در ترمینال وارد کند.www-data, mysql, postgres, mail./home/ ندارد. (پوشه خانه www-data معمولاً /var/www است)./usr/sbin/nologin یا /bin/false تنظیم شده است.www-data وارد ترمینال شود.www-data یک کاربر سیستمی است که فقط برای این ساخته شده که فرآیند NGINX (و در سناریوی ما، فرآیند اپلیکیشن ASP.NET Core) با هویت او اجرا شود و نه بیشتر.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(دفتر ثبت احوال کاربران)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 یک کاربر سیستمی و محدود شده است.builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});ForwardedHeadersOptions برای تازهکارهاlocalhost). کارمند شما IP واقعی آقای رضایی را نمیبیند.HttpContext.Connection.RemoteIpAddress)، همیشه آدرس 127.0.0.1 را خواهید دید. این برای امنیت، آمار و موقعیتیابی یک فاجعه است.http است، لینکی مانند http://mysite.com/profile تولید میکند. این کار باعث خطاهای Mixed Content در مرورگر شده یا حتی میتواند باعث یک حلقه ریدایرکت بینهایت بین NGINX و اپلیکیشن شما شود.X-Forwarded-For): "گزارشگر IP اصلی: این پیام در اصل از طرف آقای رضایی با IP 8.8.8.8 است."X-Forwarded-Proto): "گزارشگر پروتکل: ایشان از در ورودی VIP (https) وارد شدهاند."builder.Services.Configureoptions => { ... }options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;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 خواهد بود.