mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-05 20:02:47 +01:00
181 lines
11 KiB
Markdown
181 lines
11 KiB
Markdown
# CORS-прокси для получения сертификатов
|
||
|
||
Инструмент цифровой подписи использует CORS-прокси для получения сертификатов издателей у внешних удостоверяющих центров (CA). Это нужно, потому что многие серверы CA не добавляют заголовки CORS в ответы, и браузер не может запросить сертификаты напрямую.
|
||
|
||
Кроме того, многие серверы CA отдают сертификаты по обычному HTTP. Если ваш экземпляр BentoPDF работает по HTTPS, браузеры блокируют такие HTTP-запросы (политика смешанного содержимого). CORS-прокси решает обе проблемы: запросы идут через HTTPS-endpoint с нужными заголовками.
|
||
|
||
## Как это работает
|
||
|
||
При подписании PDF сертификатом:
|
||
|
||
1. Библиотека `zgapdfsigner` пытается построить полную цепочку сертификатов
|
||
2. Она запрашивает сертификаты издателей по URL из расширения AIA (Authority Information Access) вашего сертификата
|
||
3. Эти запросы идут через CORS-прокси, который добавляет нужные заголовки `Access-Control-Allow-Origin`
|
||
4. Прокси возвращает данные сертификата в браузер
|
||
|
||
## Собственный CORS-прокси
|
||
|
||
Если вы размещаете BentoPDF у себя, для работы цифровых подписей с сертификатами, которым нужна загрузка цепочки, потребуется развернуть собственный CORS-прокси.
|
||
|
||
### Вариант 1: Cloudflare Workers (рекомендуется)
|
||
|
||
1. **Установите Wrangler CLI**:
|
||
|
||
```bash
|
||
npm install -g wrangler
|
||
```
|
||
|
||
2. **Войдите в Cloudflare**:
|
||
|
||
```bash
|
||
wrangler login
|
||
```
|
||
|
||
3. **Клонируйте BentoPDF и обновите разрешённые источники**:
|
||
|
||
```bash
|
||
git clone https://github.com/alam00000/bentopdf.git
|
||
cd bentopdf/cloudflare
|
||
```
|
||
|
||
Откройте `cors-proxy-worker.js` и замените массив `ALLOWED_ORIGINS` на свой домен:
|
||
|
||
```js
|
||
const ALLOWED_ORIGINS = [
|
||
'https://your-domain.com',
|
||
'https://www.your-domain.com',
|
||
];
|
||
```
|
||
|
||
::: warning Важно
|
||
Без этого изменения прокси отклонит все запросы с вашего сайта с ошибкой **403 Forbidden**. По умолчанию разрешены только запросы с `bentopdf.com`.
|
||
:::
|
||
|
||
4. **Разверните прокси**:
|
||
|
||
```bash
|
||
wrangler deploy
|
||
```
|
||
|
||
Запомните URL своего worker (например, `https://bentopdf-cors-proxy.your-subdomain.workers.dev`).
|
||
|
||
5. **Пересоберите BentoPDF с URL прокси**:
|
||
|
||
Если вы используете Docker:
|
||
|
||
```bash
|
||
export VITE_CORS_PROXY_URL="https://your-worker.workers.dev"
|
||
DOCKER_BUILDKIT=1 docker build \
|
||
--secret id=VITE_CORS_PROXY_URL,env=VITE_CORS_PROXY_URL \
|
||
-t your-bentopdf .
|
||
```
|
||
|
||
Если вы собираете из исходников:
|
||
|
||
```bash
|
||
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
|
||
```
|
||
|
||
### Вариант 2: собственный серверный прокси
|
||
|
||
Можно создать и собственный endpoint прокси. Требования:
|
||
|
||
1. Принимать GET-запросы с параметром запроса `url`
|
||
2. Запрашивать этот URL со своего сервера (на стороне сервера ограничений CORS нет)
|
||
3. Возвращать ответ с такими заголовками:
|
||
- `Access-Control-Allow-Origin: https://your-domain.com`
|
||
- `Access-Control-Allow-Methods: GET, OPTIONS`
|
||
- `X-Content-Type-Options: nosniff`
|
||
|
||
Пример реализации на Express.js:
|
||
|
||
```javascript
|
||
app.get('/api/cert-proxy', async (req, res) => {
|
||
const targetUrl = req.query.url;
|
||
|
||
// Validate it's a certificate URL
|
||
if (!isValidCertUrl(targetUrl)) {
|
||
return res.status(400).json({ error: 'Invalid URL' });
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(targetUrl);
|
||
const data = await response.arrayBuffer();
|
||
|
||
res.set('Access-Control-Allow-Origin', 'https://your-domain.com');
|
||
res.set('Content-Type', 'application/octet-stream');
|
||
res.set('X-Content-Type-Options', 'nosniff');
|
||
res.send(Buffer.from(data));
|
||
} catch (error) {
|
||
res.status(500).json({ error: 'Proxy error' });
|
||
}
|
||
});
|
||
```
|
||
|
||
## Вопросы безопасности
|
||
|
||
В поставляемом Cloudflare Worker предусмотрено несколько мер безопасности:
|
||
|
||
| Функция | Описание |
|
||
| --------------------------- | ------------------------------------------------------------------------------------------------ |
|
||
| **Проверка источника** | Разрешает запросы только с доменов из `ALLOWED_ORIGINS` |
|
||
| **Ограничение URL** | Разрешает только URL сертификатов (`.crt`, `.cer`, `.pem`, `/certs/`, `/ocsp`, `/crl`) |
|
||
| **Блокировка частных IP** | Блокирует частные диапазоны IPv4/IPv6, link-local, loopback, десятичные IP и облачные метаданные |
|
||
| **Безопасный Content-Type** | Возвращает только безопасные MIME-типы сертификатов, блокирует подмену content-type источником |
|
||
| **Лимит размера файла** | Передаёт ответ потоком с лимитом 10 МБ и прерывает загрузку при превышении |
|
||
| **Ограничение частоты** | 60 запросов в минуту на IP или IPv6 /64 (нужен KV, при ошибках KV пропускает запросы) |
|
||
| **Подписи HMAC** | Необязательная подпись на стороне клиента (сдерживает случайные злоупотребления) |
|
||
|
||
### DNS rebinding (хостинг вне Cloudflare)
|
||
|
||
Worker отклоняет любой URL, который разрешается в частный или внутренний IP. Фиксированный список разрешённых хостов использовать нельзя, так как URL цепочек сертификатов указывают на тот CA, который выдал сертификат (FNMT, корпоративные CA и так далее), и адреса заранее неизвестны. Поэтому остаётся небольшое окно для rebinding: тот, кто управляет DNS своего домена, может отдать проверке безопасности публичный IP, а настоящему запросу чуть позже частный.
|
||
|
||
В Cloudflare это не эксплуатируется: Workers не могут обращаться к частным IP и endpoint метаданных `169.254.169.254`, поэтому rebind ни к чему не приведёт. Только не давайте worker доступа внутрь: не подключайте Tunnel, Service или привязку к частной сети (ему нужен лишь `RATE_LIMIT_KV`).
|
||
|
||
Если вы запускаете его вне Cloudflare (workerd, Node и подобное) внутри VPC, среда _может_ обращаться к внутренним IP. Поставьте перед ней исходящий файрвол и заблокируйте частные диапазоны: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `127.0.0.0/8`, `169.254.0.0/16` и их аналоги IPv6. Код приложения не может этого обеспечить, когда вы вне Cloudflare.
|
||
|
||
## Отключение прокси
|
||
|
||
Если CORS-прокси вам не нужен, задайте переменной окружения пустую строку:
|
||
|
||
```
|
||
VITE_CORS_PROXY_URL=
|
||
```
|
||
|
||
**Примечание**: без прокси подпись сертификатами, которым нужна внешняя загрузка цепочки (например, FNMT или некоторые корпоративные CA), завершится ошибкой "Failed to fetch".
|
||
|
||
## Устранение неполадок
|
||
|
||
### "Signing error: TypeError: Failed to fetch"
|
||
|
||
Обычно это означает одно из двух:
|
||
|
||
1. **CORS-прокси не настроен**: задайте `VITE_CORS_PROXY_URL` и пересоберите проект
|
||
2. **Смешанное содержимое заблокировано**: ваш сайт работает по HTTPS, а URL издателя сертификата по HTTP. CORS-прокси решает эту проблему.
|
||
3. **CORS-прокси отклоняет ваш источник**: проверьте, что ваш домен есть в массиве `ALLOWED_ORIGINS` в `cors-proxy-worker.js`
|
||
|
||
### "403 Forbidden" от прокси
|
||
|
||
Вашего домена нет в списке `ALLOWED_ORIGINS`. Отредактируйте `cors-proxy-worker.js`:
|
||
|
||
```js
|
||
const ALLOWED_ORIGINS = ['https://your-domain.com'];
|
||
```
|
||
|
||
Затем разверните заново: `npx wrangler deploy`
|
||
|
||
### Проверка прокси
|
||
|
||
```bash
|
||
curl -H "Origin: https://your-domain.com" \
|
||
"https://your-proxy.workers.dev?url=http://www.cert.fnmt.es/certs/ACUSU.crt"
|
||
```
|
||
|
||
### Сертификаты, работающие без прокси
|
||
|
||
Некоторые сертификаты содержат полную цепочку в файле P12/PFX и не требуют внешней загрузки:
|
||
|
||
- Самоподписанные сертификаты
|
||
- Некоторые коммерческие CA, которые включают промежуточные сертификаты
|
||
- Сертификаты, для которых вы вручную собрали полную цепочку
|