Application Security

تأمین توابع Serverless: بهترین شیوه‌های اعتبارسنجی ورودی و IAM

معماری‌های Serverless نحوه ساخت و استقرار برنامه‌ها را متحول کرده‌اند و مقیاس‌پذیری و کاهش بار عملیاتی را ارائه می‌دهند. با این حال، نام «بدون سرور» گاهی می‌تواند حس امنیت کاذبی ایجاد کند. صرفاً به این دلیل که شما زیرساخت را مدیریت نمی‌کنید، به این معنی نیست که مسئولیت امنیت منطق برنامه با شما نیست. در واقع، ماهیت موقتی توابع Serverless، حمله‌پذیری‌های منحصر‌به‌فردی را ایجاد می‌کند که نیازمند تغییر در استراتژی امنیت است.

دو ستون اصلی در پیش‌زمینه امنیت Serverless قرار دارند: اعتبارسنجی ورودی دقیق و سیاست‌های سخت‌گیرانه مدیریت هویت و دسترسی (IAM). این پست بررسی می‌کند که چرا این موارد حیاتی هستند و نمونه‌های کد عملیاتی را برای مستحکم‌سازی استقرار شما ارائه می‌دهد.

توهم اعتماد: چرا اعتبارسنجی ورودی غیرقابل مذاکره است

وقتی به مدل Serverless مهاجرت می‌کنید، توابع شما اغلب مستقیماً از طریق دروازه‌های API (API Gateways) در معرض دید قرار می‌گیرند یا توسط منابع رویداد مانند سطل‌های S3 یا جریان‌های DynamoDB فعال می‌شوند. مهاجمان قطعاً تلاش خواهند کرد تا داده‌های نامعتبر، بیش‌ازحد بزرگ یا مخرب را به این نقاط پایانی ارسال کنند. بدون اعتبارسنجی، کد شما در برابر تزریق NoSQL، سرریز بافر و خطاهای زمان اجرا که می‌تواند منجر به توقف خدمات (DoS) شود، آسیب‌پذیر است.

همیشه فرض کنید که تمام ورودی‌ها خصمانه هستند. چه داده از یک هدر HTTP، چه از بدنه JSON و چه از رشته پرس‌وجو (Query String) بیاید، باید قبل از تماس با منطق کسب‌وکار شما، پاک‌سازی و اعتبارسنجی شود. این موضوع نه تنها برای داده‌های ارائه‌شده توسط کاربر، بلکه برای رویدادهای تولیدشده توسط سیستم نیز صدق می‌کند.

پیاده‌سازی اعتبارسنجی در Node.js

برای توابع Lambda مبتنی بر Node.js، کتابخانه‌هایی مانند Zod یا Joi اعتبارسنجی طرحواره (Schema) قدرتمندی ارائه می‌دهند. در زیر نمونه‌ای با استفاده از Zod برای اعتبارسنجی داده‌های ورودی JSON آورده شده است. این کار تضمین می‌کند که ساختار داده با انتظارات مطابقت دارد و انواع داده صحیح هستند، پیش از آنکه پردازش آغاز شود.

const { z } = require('zod');

// Define the schema for a user profile
const UserSchema = z.object({
  email: z.string().email("Invalid email format"),
  age: z.number().int().min(0).max(150, "Age must be realistic"),
  username: z.string().min(3).max(30).regex(/^[a-zA-Z0-9_]+$/, "Username must be alphanumeric")
});

exports.handler = async (event) => {
  try {
    // Parse and validate the incoming event body
    const validatedData = UserSchema.parse(event.body);
    
    // Proceed with business logic using validatedData
    console.log("Validated user:", validatedData);
    
    return {
      statusCode: 200,
      body: JSON.stringify({ message: "Success" })
    };
  } catch (error) {
    // Handle validation errors gracefully
    return {
      statusCode: 400,
      body: JSON.stringify({ error: error.errors })
    };
  }
};

با رد داده‌های نامعتبر در نقطه ورود، شما از فساد داده‌ها در مراحل بعدی جلوگیری کرده و سطح حمله را کاهش می‌دهید. این رویکرد همچنین به عنوان نوعی کد خودمستند عمل می‌کند و به وضوح تعریف می‌کند که تابع شما چه انتظاری دارد.

کمترین امتیاز: تسلط بر IAM برای Serverless

در حالی که اعتبارسنجی ورودی از کد شما در برابر داده‌های بد محافظت می‌کند، سیاست‌های IAM از داده‌های شما در برابر کد مخرب یا به‌دست‌آمده محافظت می‌کنند. در محیط‌های سنتی سرور، توسعه‌دهندگان اغلب به دلایل راحتی، مجوزهای گسترده‌ای اعطا می‌کنند. در Serverless، این یک اشتباه فاجعه‌بار است.

اصل کمترین امتیاز (Least Privilege) حکم می‌کند که هر تابع باید تنها حداقل مجوزهای لازم برای انجام وظیفه خاص خود را داشته باشد. اگر یک تابع Lambda فقط نیاز به خواندن از یک جدول خاص DynamoDB دارد، هرگز نباید مجوز حذف موارد، خواندن از جداول دیگر یا دسترسی به سطل‌های S3 را داشته باشد.

تعریف مجوزهای دانه‌دانه (Granular)

تابعی را در نظر بگیرید که پروفایل کاربر را در DynamoDB به‌روز می‌کند. یک مبتدی ممکن است به تابع AdministratorAccess یا مجوزهای گسترده DynamoDB اعطا کند. در عوض، ما باید یک سیاست IAM خاص بسازیم.

در اینجا یک سند نمونه سیاست IAM آورده شده است که تابع Lambda را محدود به انجام عملیات UpdateItem فقط روی یک ARN (نام منبع مشخص) خاص می‌کند:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:UpdateItem"
      ],
      "Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/UserProfiles/index/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    }
  ]
}

به دو نکته در اینجا توجه کنید. اول، عمل محدود به UpdateItem است؛ تابع نمی‌تواند بخواند یا حذف کند. دوم، مجوزهای ثبت رویداد (Logging) به صورت جداگانه اعطا می‌شوند، که تضمین می‌کند اگر تابع مورد حمله قرار گیرد، مهاجم نمی‌تواند به راحتی ردپای خود را با غیرفعال کردن ثبت رویداد پنهان کند، هرچند که همچنان به سایر منابع حیاتی دسترسی نخواهد داشت.

نتیجه‌گیری

امنیت توابع Serverless نیازمند یک ذهنیت پیش‌دستانه است. شما نمی‌توانید تنها به دفاع‌های مرزی تکیه کنید؛ باید کد را از طریق اعتبارسنجی ورودی دقیق ایمن کنید و دامنه تخریب هرگونه نقض امنیتی احتمالی را از طریق سیاست‌های IAM دانه‌دانه محدود نمایید. با پیاده‌سازی این شیوه‌ها، تضمین می‌کنید که برنامه‌های Serverless شما در برابر تهدیدات مدرن مقاوم، قابل اعتماد و امن باقی بمانند. به یاد داشته باشید، در Serverless، امنیت یک افزونه نیست—بلکه یک جزء اساسی از معماری شماست.

Share: