Статья по безопасности Serverless / FaaS (Serverless / FaaS Security Cheat Sheet)

Введение

Вычисления в модели serverless (функции как сервис — FaaS), например AWS Lambda, Azure Functions и Google Cloud Functions, упрощают разработку и масштабирование приложений. При этом модель выполнения (короткоживущие событийно-ориентированные функции в управляемой среде) создаёт особые риски безопасности по сравнению с классической архитектурой.

Эта статья описывает практики защиты serverless-приложений и снижения поверхности атаки.

Ключевые риски

  • Избыточные права функций (широкие роли IAM, политики с *).
  • Непроверенные данные событий (API Gateway, S3, Pub/Sub, IoT).
  • Утечки при холодном старте и в контексте выполнения (состояние, побочные каналы по времени).
  • Злоупотребление цепочками вызовов (скомпрометированная функция вызывает другие).
  • Риски общей среды (утечки в мультитенантности, повторное использование /tmp).
  • Секреты в коде или переменных окружения.
  • Чрезмерный сетевой доступ.

Лучшие практики

1. Принцип наименьших привилегий

  • Назначайте функциям минимально необходимые права IAM.
  • Отдельная роль на функцию (избегайте общих ролей с высокими привилегиями).
  • Ограничивайте ключи к БД и API минимальным набором операций.

Плохая политика IAM (слишком широкая):

{
  "Effect": "Allow",
  "Action": "*",
  "Resource": "*"
}

Хорошая политика IAM (с ограничением области):

{
  "Effect": "Allow",
  "Action": ["dynamodb:GetItem", "dynamodb:PutItem"],
  "Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/Orders"
}

2. Изоляция окружений

  • Отключайте сетевой доступ по умолчанию, если он не нужен (например исходящий в интернет).
  • Размещайте функции в приватных подсетях с контролируемым egress.
  • Изолируйте чувствительные функции (платежи, аутентификация) от универсальных.
  • Разделяйте продакшен и стенды жёсткими границами.

Конфигурация VPC для AWS Lambda (ограничительная):

VpcConfig:
  SubnetIds:
    - subnet-123456
  SecurityGroupIds:
    - sg-restrict-outbound

3. Безопасный вызов функций

  • Требуйте аутентификацию и авторизацию для всех триггеров (API Gateway, Pub/Sub, S3, IoT).
  • Вызовы «функция — функция» подтверждайте подписанными токенами или workload identity.
  • Применяйте rate limiting и throttling против DoS и злоупотреблений.

Пример авторизатора API Gateway (проверка JWT):

{
  "Type": "JWT",
  "IdentitySource": "$request.header.Authorization",
  "Issuer": "https://secure-idp.example.com/",
  "Audience": "my-api-client"
}

4. Валидация данных события

  • Считайте любое тело события недоверенным вводом.
  • Применяйте строгую проверку и при необходимости нормализацию (длина, тип, формат).
  • Защищайтесь от типовых инъекций (SQLi, XSS, JSON-инъекция, небезопасная десериализация).
  • Удаляйте лишние поля и метаданные до обработки.

Пример: проверка ввода в Python для Lambda

import json
import re

def lambda_handler(event, context):
    body = json.loads(event["body"])
    email = body.get("email", "")

    if not re.match(r"[^@]+@[^@]+\.[^@]+", email):
        return {"statusCode": 400, "body": "Invalid email"}

    # безопасная обработка
    return {"statusCode": 200, "body": "OK"}

5. Холодный старт и безопасность контекста выполнения

  • Не полагайтесь на то, что среда выполнения «чиста» между вызовами.
  • Не храните секреты и временные чувствительные данные в глобальных или статических переменных.
  • Учитывайте побочные каналы (разница во времени, оставшиеся файлы в /tmp).
  • Для критичных нагрузок используйте одноразовые среды выполнения, если платформа это поддерживает.

Плохо:

# Секрет остаётся в глобальной переменной между вызовами
SECRET_KEY = "hardcoded-secret"

Хорошо:

import os
from my_secrets_lib import get_secret

def lambda_handler(event, context):
    secret = get_secret("db-password")  # получать заново при необходимости
    ...

6. Управление секретами

  • По возможности не храните секреты в переменных окружения.
  • Получайте секреты из хранилищ, используя локальные расширения с кэшем, а не только env.
  • Используйте краткоживущие учётные данные (STS, федерация workload identity).
  • Автоматизируйте ротацию секретов.

Получение секрета в AWS Lambda (Python через Parameters and Secrets Extension):

Важно: для функции нужно подключить и включить расширение AWS Lambda Parameters and Secrets Extension (например слоём Lambda). Иначе http://localhost:2773 будет недоступен, и запросы к нему завершатся ошибкой.

import os
import json
import urllib.request
import urllib.parse

def get_secret(secret_name):
    """
    Читает секрет через локальный эндпоинт расширения Lambda.
    Быстрее и дешевле за счёт локального кэша.
    """
    # Расширение слушает локальный HTTP на порту 2773
    encoded_name = urllib.parse.quote_plus(secret_name)

    secrets_extension_endpoint = (
        f"http://localhost:2773/secretsmanager/get?secretId={encoded_name}"
    )

    # Аутентификация токеном окружения Lambda
    session_token = os.environ.get("AWS_SESSION_TOKEN")
    if not session_token:
        raise RuntimeError(
            "Missing AWS_SESSION_TOKEN required for "
            "Parameters and Secrets Extension authentication."
        )

    headers = {
        "X-Aws-Parameters-Secrets-Token": session_token
    }

    request = urllib.request.Request(
        secrets_extension_endpoint,
        headers=headers,
        method="GET"
    )

    with urllib.request.urlopen(request, timeout=3) as response:
        if response.status != 200:
            raise Exception(
                f"Error retrieving secret: {response.read().decode('utf-8')}"
            )

        response_json = json.loads(response.read())
        secret_string = response_json["SecretString"]

        return json.loads(secret_string)

См. также Secrets Management Cheat Sheet.

7. Мониторинг и журналирование

  • Централизованные логи (CloudWatch, Azure Monitor, GCP Logging).
  • Маскируйте секреты и персональные данные.

Пример: редактирование полей перед записью в лог

import logging

def log_event(event):
    safe_event = {k: ("***" if "password" in k else v) for k, v in event.items()}
    logging.info(safe_event)

8. Безопасность цепочки поставки

  • Сканируйте зависимости (npm audit, pip-audit, safety).
  • Используйте минимальные пакеты для деплоя.
  • Подписывайте пакеты и проверяйте контрольные суммы.

Проверка хеша слоя AWS Lambda:

shasum -a 256 layer.zip

Стоит и не стоит

Стоит:

  • Навязывать наименьшие привилегии для каждой функции.
  • Проверять все входные данные событий.
  • Брать секреты из хранилищ, а не только из переменных окружения.
  • Ограничивать исходящий сетевой трафик.
  • Мониторить вызовы и логи.

Не стоит:

  • Вшивать секреты в код или конфигурацию в репозитории.
  • Считать, что среда выполнения между вызовами гарантированно чистая.
  • Выдавать права IAM с *.
  • Оставлять чувствительные данные в /tmp или глобальных переменных.
  • Слепо доверять источникам событий.

Ссылки

© Перевод на русский язык. Оригинальные материалы: OWASP Cheat Sheet Series.
Этот проект использует материалы OWASP, распространяемые по лицензии CC BY-SA 4.0.