Как импортировать заявки из публичной формы в частную сеть
Проследите одну заявку из публичной формы от записи приёма до частного импортёра и держите проверку, учётные данные и обработку повторов на правильной стороне.
Публичная форма, которая никогда не общается с частной сетью, всё равно создаёт данные, которые частная сеть должна оценить. Туннель меняет только то, кто кому звонит. Всё после загрузки — это решение об импорте, и оно принадлежит частной стороне. Это руководство прослеживает одну заявку от браузера до зафиксированной внутренней записи.
Начните с того места, куда попадает заявка
Браузер отправляет запрос на публичную конечную точку, которая записывает строку в контролируемое хранилище приёма и возвращает не больше чем подтверждение. Это хранилище — точка передачи, и оно должно быть единственным, куда может писать публичный уровень. Храните там необработанные данные без изменений, вместе со временем поступления и стабильным идентификатором записи.
Затем частный обработчик вытягивает ожидающие строки через туннель и решает, что делать с каждой. Он работает под собственной учётной записью, и это тема следующего раздела.
curl --silent --fail \
--cert /etc/importer/client.crt --key /etc/importer/client.key \
"https://intake.corp.internal/api/pending?limit=20" \
-o /var/lib/importer/pending.json
Дайте импортёру только те права, которые ему нужны
Публичный веб-процесс и учётная запись, которая пишет во внутреннее приложение, должны быть двумя разными субъектами. Публичному серверу не нужны внутренние административные права только потому, что он должен доставлять данные формы, а одна общая учётная запись для обеих сторон превращает любую веб-ошибку во внутреннюю запись. Выдайте обработчику собственные учётные данные, ограничьте их одной операцией импорта и фиксируйте, какой субъект выполнил каждый импорт. Именно это разделение делает границу туннеля осмысленной, а не декоративной.
Ограничьте и то, из какого источника обработчик может читать. Если реализация использует очередь или сервис передачи, этот сервис сохраняет собственную аутентификацию и политику доступа, потому что VPN не аутентифицирует ничего внутри полезной нагрузки.
Краткоживущая функция не освобождается от той же проверки. Пока она работает, её сетевое положение и учётные данные всё ещё дают права, а короткий срок жизни лишь сужает окно. Срок жизни не заменяет решение о том, какие операции может выполнять субъект.
Проверяйте до того, как что-то изменится внутри
Поля публичной формы — это контролируемый злоумышленником текст, пока не доказано обратное, и рекомендации OWASP по проверке ввода относят серверные потоки к недоверенным источникам именно поэтому. Зашифрованная передача означает, что байты пришли целыми, а не что они описывают допустимый запрос. Разбирайте и проверяйте до любого привилегированного шага, чтобы плохая строка никогда не дошла до кода, способного действовать.
Определите допустимые поля, их форматы, размеры и бизнес-правила, которые должны соблюдаться. Дата может быть правильно оформлена и всё равно неверна для запрошенной операции, а полезная нагрузка, называющая внутреннюю учётную запись или статус утверждения, не должна иметь возможности выдать себе ни то, ни другое.
Отправляйте отклонённые строки в результат проверки, а не угадывайте исправление и не передавайте их в оболочку или сырой интерфейс базы данных. Записывайте идентификатор записи и сработавшее правило, а не каждое поле, введённое заявителем.
Сделайте точку повтора на одной операции
Если запись submission-1042 вытягивается дважды, потому что первое подтверждение потерялось, обработчику нужен устойчивый способ распознать исходную запись и её зафиксированный результат. Расписание, которое запускает задачу один раз, не доказывает, что каждая строка применена один раз. Повторная доставка нормальна, поэтому проект должен её поглощать.
Используйте собственную транзакцию и механизм обработки дубликатов внутреннего приложения и держите идентификатор источника, результат и подтверждение в одной согласованной последовательности. Подтверждайте только после того, как нужная операция зафиксирована или устойчиво назначена, и никогда не удаляйте строку приёма только потому, что байты были загружены.
Затем докажите это тремя фикстурами: корректной строкой, некорректно оформленной строкой и тем же контролируемым идентификатором источника, доставленным дважды. Каждая из них проходит через ту же конечную точку, которую использует обработчик.
for f in fixtures/valid-submission.json fixtures/bad-date.json; do
curl --silent --fail --cert /etc/importer/client.crt \
-X POST --data @"$f" https://intake.corp.internal/api/records
done
curl --silent --fail --cert /etc/importer/client.crt \
-X POST --data @fixtures/valid-submission.json https://intake.corp.internal/api/records
Первые две строки должны закончиться в разных внутренних состояниях, а третья должна вернуть идентификатор, уже зафиксированный для этой исходной записи, вместо создания второго. Всё остальное означает, что импорт пока не повторяем.