cdrop — техническое руководство
Версия 0.22.0
2026.11.05
Руководство описывает назначение, архитектуру, API, конфигурацию, токены доступа, интеграцию и развёртывание cdrop.
1. Назначение сервиса
cdrop — небольшой HTTP-сервис для
аутентифицированной публикации короткоживущих текстовых
артефактов с выдачей по короткой ссылке и/или QR-коду и
ограниченным числом получений.
Сервис решает одну задачу:
Опубликовать текст по секретному токену, получить короткий URL, отдать этот текст не более N раз за TTL, затем забыть.
cdrop не является:
- центром сертификации (CA);
- хранилищем ключей или секретов;
- мессенджером;
- долговременным архивом;
- файлообменником произвольных файлов;
- сервисом для передачи паролей и приватных ключей.
Это транспорт, а не источник доверия. Получатель обязан сам проверить подпись, цепочку или происхождение артефакта.
1.1. Что публикуется
Принимаются артефакты трёх классов:
- артефакты cnet — armored-блоки
CNET INVITATION,CNET REQUEST,CNET PASSи другие блоки с префиксомCNET; - публичные PEM — сертификаты X.509 (включая цепочки), CSR, CRL, открытые ключи, PKCS#7;
- SSH — публичные ключи и SSH-сертификаты в формате
authorized_keys.
Проверяются только формат, число блоков и общий размер. Подписи, сеть и цепочки сертификатов сервис не проверяет: при публикации по токену гарантией является токен, при публикации через форму — только captcha и квоты. Подлинность материала получатель проверяет сам.
2. Зачем понадобился cdrop
Артефакты cnet — обычный текст размером 1.3–4.5 КБ. Передавать их неудобно:
- мессенджеры и почта ломают переводы строк и armor;
- QR в терминале 80×25 вмещает лишь очень короткий текст;
- полный Паспорт (4.4 КБ) не помещается в один QR даже при уровне коррекции L;
- копирование через буфер обмена на телефон — лишние шаги и ошибки;
- долгоживущие ссылки и публичные pastebin-сервисы не дают контроля TTL и числа получений.
cdrop даёт:
- короткий URL (~35 символов), помещающийся в QR v2–v4;
- TTL от часа до недели;
- не более трёх получений;
- единый формат публикации без проверки подписи и цепочки;
- отсутствие аккаунтов и пользовательских интерфейсов;
- минимальную поверхность атаки.
3. Востребованность
3.1. Кто и зачем использует
- PKI-команды: передать CSR, сертификат, CRL, цепочку или публичный ключ коллеге или на телефон.
- DevOps/SRE: опубликовать публичный ключ SSH, тестовый сертификат или цепочку на несколько часов.
- cnet: Приглашение → Заявка → Паспорт без мессенджеров и ручного копирования.
3.2. Похожие сервисы и отличие
| Класс | Примеры | Отличие cdrop |
|---|---|---|
| Pastebin | PrivateBin, Hastebin | нет TTL-политики и лимита получений в короткой ссылке |
| Одноразовые секреты | onetimesecret, Yopass, Bitwarden Send | cdrop не E2E и не для секретов |
| Файлообмен | transfer.sh, file.io | cdrop принимает только поддерживаемый текст (cnet/PEM/SSH) и требует токен |
| Гит-хранилища | Gist, S3 presigned | долгоживущие, неудобны для QR и телефона |
| PKI-инструменты | step-ca, CFSSL | выпускают, но не передают артефакт человеку |
cdrop занимает узкую нишу: публичные артефакты,
короткий срок, короткая ссылка, обязательная авторизация
публикации.
3.3. Честная оценка
Это не массовый SaaS и вряд ли им станет. Ценность — инфраструктурный инструмент и побочный продукт cnet. Основной спрос — внутри инфраструктурных и PKI-команд. Для секретов и приватных ключей следует использовать специализированные E2E-инструменты.
4. Архитектура
4.1. Слои
cmd/cdrop CLI и сервер
internal/config YAML/JSON, переменные окружения
publish контракт Validator, Item{Kind, Scope}, Quota
store SQLite/PostgreSQL, атомарное получение
server HTTP, лимиты, квоты, метрики, страницы
artifact валидатор cnet/PEM/SSH по формату
web встроенные HTML-страницы
4.2. Независимость от cnet
Ядро (publish, store, server,
web, artifact) не импортирует
cnet/auth и cnet/uid. Инвариант
проверяется:
make -C cdrop check-coreПроверки подписи, доверия и цепочки в ядре нет. Это позволяет использовать ядро в других проектах и развивать cdrop как отдельный продукт.
4.3. Что принимает сервис
Сервис принимает артефакты трёх классов: cnet, публичные PEM и SSH.
Один экземпляр обслуживает все классы; отдельная настройка политики не
требуется. Классы в одной публикации не смешиваются, но цепочка из
нескольких PEM-блоков — это одна публикация класса pem.
Сервис проверяет формат, число блоков и общий размер; подписи и цепочки
сертификатов не проверяются.
5. Как работает сервис
5.1. Публикация
- Клиент вызывает
POST /api/v1/objectsсAuthorization: Bearer. - Проверяется токен, размер, TTL.
- Валидатор проверяет формат тела:
- cnet — корректность armor;
- pem — допустимые типы блоков (цепочки разрешены), запрет приватных ключей;
- ssh — типы ключей/сертификатов, base64 и соответствие типа.
- Проверяются квоты токена.
- Генерируется ID (12 символов Crockford Base32, 60 бит).
- Тело сохраняется с TTL и остатком получений.
- Возвращаются
id,url,expires_at,remaining,sha256.
5.2. Получение
GET /o/<ID>— HTML-страница;GET /o/<ID>.txt— тот же текст какtext/plain;- каждое успешное получение уменьшает остаток;
HEADи метаданные остаток не уменьшают;- после нуля или TTL —
410 Gone; - неизвестный ID —
404; - получение атомарно:
UPDATE ... RETURNINGне отдаёт больше тел, чем осталось.
5.3. Удаление
DELETE /api/v1/objects/<ID> требует токен.
Удаление мягкое: остаток обнуляется, срок истекает, объект отдаёт
410 до цикла очистки, затем строка удаляется.
5.4. Очистка
Фоновый цикл раз в 10 минут удаляет истёкшие и мягко удалённые записи, затем укладывает БД в предел объёма. Тела артефактов не хранятся дольше TTL плюс цикл очистки.
5.5. Предел объёма БД (drop head)
Если БД может быть недоверенной или публикация идёт бесконечно, объём ограничивается конфигом:
max-db-bytes: 1073741824 # 1 ГиБ; -1 - без предела
max-objects: 0 # 0 - без пределаПеред вставкой в той же транзакции считается COUNT(*) и
SUM(size). Если новая запись не помещается, самые старые
публикации (created_at, затем id) удаляются
пачками по 100 до освобождения места. Это защищает СУБД от переполнения
при бесконечной публикации и взломе. Если артефакт больше предела,
сервис возвращает 507 Insufficient Storage. В PostgreSQL
вытеснение сериализуется advisory lock. size ведётся с
миграции 6 и равен размеру открытого артефакта (шифрование at-rest
добавляет небольшую служебную нагрузку).
5.6. Страницы
/— домашняя страница узла (в тестовом контуре —lab917);/ru/— то же на русском;/cdrop— форма публикации (EN) или справка API;/cdrop/api— полная справка API (EN);/ru/cdrop— то же на русском;/ru/cdrop/api— полная справка API (RU);/cdrop/manualи/ru/cdrop/manual— полное руководство (HTML);/o/<ID>— страница артефакта с копированием и скачиванием.
6. HTTP API
База: https://<host>.
6.1. Публикация
POST /api/v1/objects
Authorization: Bearer <publish-token|grant-token>
Content-Type: text/plain; charset=utf-8
X-CDROP-TTL: 24h
X-CDROP-GRANT: request=3
X-CDROP-GRANT необязателен и работает только с
publish-токеном: он просит выдать вместе с публикацией ограниченный
грант. kind должен принадлежать области артефакта
(request для cnet, pem для PEM,
ssh-key/ssh-cert для SSH); uses —
от 1 до grants.max-uses.
Тело — armor cnet, публичные PEM-блоки (возможна цепочка) или строки
SSH. Если грант запрошен, сервис встраивает в артефакт полную ссылку
https://<host>/p/<token> (armor/PEM —
заголовком X-CDROP-GRANT, SSH — комментарием
# X-CDROP-GRANT: ...).
Ответ 201 Created:
{
"id": "7Q2M9K4TABCD",
"url": "https://drop.example/o/7Q2M9K4TABCD",
"expires_at": "2026-11-05T12:00:00Z",
"remaining": 3,
"sha256": "9f2c...",
"grant": {
"kind": "request",
"url": "https://drop.example/p/cdg_...",
"remaining": 3,
"expires_at": "2026-11-05T12:00:00Z"
}
}Поле grant присутствует, только если грант запрошен или
запрос выполнен токеном гранта (тогда url пуст, а
remaining — остаток публикаций по гранту).
Коды ошибок:
| Код | Причина |
|---|---|
400 |
не артефакт, пустое тело, неверный X-CDROP-TTL или
грант |
401 |
токен отсутствует/неверен/истёк/отключён |
403 |
токену не разрешена область, тип не тот, вложенный грант |
410 |
грант истёк или исчерпан |
413 |
тело больше предела |
422 |
приватный ключ, недопустимый блок, смешение классов, больше 16 блоков |
429 |
лимит или квота (для квоты Retry-After: 3600) |
507 |
предел объёма БД, освободить место не удалось |
Ответы 400/422/403 обезличены; подробности — только в
журнале.
6.1.1. Ограниченный грант
Грант — capability-ссылка: кто её получил, может N раз опубликовать артефакт указанного типа без publish-токена. Грант:
- хранится в строке публикации-родителя и исчезает вместе с ней (DELETE, takedown, истечение, вытеснение);
- не даёт метаданных, удаления, панели администратора и формы с captcha;
- не может выдать новый грант:
X-CDROP-GRANTпо токену гранта —403; - хранится в БД только как SHA-256; в журнал попадает grant-id;
- расход и вставка дочерней публикации выполняются одной транзакцией.
6.2. Получение
GET /o/<ID>
GET /o/<ID>.txt
| Код | Значение |
|---|---|
200 |
тело артефакта |
404 |
неизвестный ID |
410 |
истёк, исчерпан или удалён |
Заголовки страниц: Cache-Control: no-store,
X-Robots-Tag: noindex,
Referrer-Policy: no-referrer,
Content-Security-Policy (script nonce).
6.3. Метаданные
GET /api/v1/objects/<ID>
Authorization: Bearer <publish-token>
{
"id": "7Q2M9K4TABCD",
"kind": "invite",
"scope": "cnet",
"size": 1302,
"sha256": "9f2c...",
"created_at": "2026-11-04T12:00:00Z",
"expires_at": "2026-11-05T12:00:00Z",
"remaining": 2
}kind — invite, request,
passport, pem, ssh-key,
ssh-cert или cnet; scope —
cnet, pem или ssh. Метаданные
содержат тип и отпечаток, поэтому требуют токен. Без токена —
401; чужая область — 403.
6.4. Удаление
DELETE /api/v1/objects/<ID>
Authorization: Bearer <publish-token>
204 — удалено; 401 — токен;
403 — область; 404 — нет ID; 410
— уже истёк/исчерпан/удалён.
6.5. Веб-форма публикации
GET /cdrop
GET /cdrop/api
GET /publish
POST /publish
GET /ru/publish
POST /ru/publish
GET /p/<token>
POST /p/<token>
GET /ru/p/<token>
POST /ru/p/<token>
/p/<token> — форма публикации по гранту: без
captcha и без поля токена, принимает только тип артефакта гранта,
показывает остаток публикаций. При успехе — ссылка, QR и остаток; при
исчерпании — 410 Gone.
/cdrop — точка входа: при включённой форме
(web-publish: true) отдаёт форму публикации, иначе справку
API. Справка всегда доступна по /cdrop/api; в форме есть
ссылка «API reference». Русские варианты — /ru/cdrop и
/ru/cdrop/api.
GET /publish отдаёт HTML-форму: artifact, TTL
(1h/24h/168h) и captcha. Если гранты включены, в форме есть чек-бокс
«выдать грант»; число публикаций берётся из grants.uses (по
умолчанию 3), тип выбирается по области: request для cnet, pem для PEM,
ssh-key/ssh-cert для SSH. POST проверяет captcha
(собственная арифметика, in-memory, TTL 10 минут, привязка к IP,
одноразовая) и публикует артефакт без publish-токена. Действуют лимит
10/мин и квота на IP (по умолчанию 20 публикаций, 1 МиБ, 50
активных).
Тексты формы единые: описание перечисляет cnet, публичные PEM
(включая цепочки) и SSH; placeholder —
-----BEGIN CNET INVITATION-----.
Результат — HTML со ссылкой, кнопкой Copy и QR-кодом (PNG data URI). Если грант запрошен, дополнительно показываются полная ссылка на грант, Copy и QR; ссылка также встроена в сам артефакт. Форма принимает все поддерживаемые артефакты; подписи и цепочки не проверяются, поэтому особенно важны квоты, abuse и takedown. Captcha — только speed bump, не защита от целевых ботов.
6.6. Панель администратора
GET /admin
GET /ru/admin
POST /admin/delete
POST /admin/delete-all
Вход — форма /admin/login (или
/ru/admin/login): вводится действующий publish-токен (не
отозванный, не disabled, не истёкший). Сессия хранится в
HttpOnly/Secure/SameSite=Strict cookie 30 минут (галочка «Запомнить меня
на 30 дней» — 30 суток); для curl поддержаны Basic и Bearer. Панель
доступна только по TLS. В cookie хранится случайный ID сессии, а не
publish-токен; сессии сохраняются в
state-dir/admin_sessions.json (0600) и переживают рестарт
сервиса. Без входа браузер перенаправляется на форму, API получает
401 и WWW-Authenticate.
Таблица показывает все строки objects, включая истёкшие
и исчерпанные, без тел: ID, тип, область, размер, создание, срок,
остаток, число получений, token-id, IP публикатора, источник
(api/web), User-Agent и статус. Сортировка —
по заголовкам, включая IP и источник; страницы по 25/50/100.
IP — адрес TCP-соединения; за реверс-прокси это адрес прокси. IP и User-Agent хранятся только в строке публикации, не пишутся в журнал и удаляются вместе с артефактом. User-Agent обрезается до 200 символов, управляющие символы заменяются пробелами.
Клик по ID открывает карточку артефакта: тело, IP, источник, клиент и остальные метаданные. Просмотр в панели (а также с админ-сессией или publish-токеном) не уменьшает остаток получений; обычный получатель по короткой ссылке уменьшает.
Удаление жёсткое: выбранные строки или все сразу исчезают из БД и
освобождают место. Формы защищены CSRF (double-submit cookie), для
удаления всех нужно подтверждение. Выход — кнопка «Выйти» в шапке
(POST /admin/logout).
6.7. Служебные
GET /healthz—200 ok, если БД доступна, иначе503;GET /readyz—200 ready, если БД и схема читаются, иначе503;GET /.well-known/security.txt— abuse-контакт изabuse-contact;GET /metrics— только loopback или поCDROP_METRICS_TOKEN, иначе404.
6.8. Хранилище бинарных файлов (store)
Начиная с 0.24.0 cdrop умеет публиковать не только текстовые
артефакты, но и дистрибутивы: APK, ZIP, TGZ и любые другие файлы. Это
отдельный модуль binstore; он включается режимом
сервиса.
6.8.1. Режимы
Режим задаётся полем mode:
pki— только PKI/CNET-артефакты (поведение по умолчанию);store— только хранилище бинарных файлов;pki+store— оба сервиса одновременно.
В режиме pki маршруты /admin/store и
/s/<token> отсутствуют, а таблицы bin_*
не создаются. В режиме store отключены публикация PKI/CNET
и /admin перенаправляет в /admin/store.
6.8.2. Вход и загрузка
Хранилище доступно только администратору по тому же токену, что и
панель /admin (форма входа, Basic или Bearer). Анонимной
загрузки нет.
- Войдите в
/adminи нажмите «Хранилище» (или сразу откройте/admin/store). - Нажмите «Загрузить файл».
- Задайте описание и TTL в днях, затем выберите файл.
0— хранить вечно,365— один год. - Во время загрузки показываются проценты, объём и скорость; кнопка
«Отмена» прерывает передачу. Файл пишется потоково, без промежуточной
копии в
/tmp. - После загрузки откроется карточка объекта: имя, описание, размер, TTL, даты загрузки и истечения, SHA-256, статус.
Таблица объектов поддерживает сортировку по порядку загрузки, имени, размеру, TTL и описанию, а также пагинацию 25/50/100. Удаление объекта удаляет и файл, и все его ссылки.
6.8.3. Короткие ссылки и QR
В карточке объекта задайте заметку (для кого ссылка) и максимум
скачиваний. 0 означает неограниченное число скачиваний —
режим для очень близких родственников или доверенных коллег.
Сервис выдаёт короткую ссылку
https://<base-url>/s/<token> и страницу с
QR-кодом. В таблице ссылок видны заметка, объект, максимум, фактическое
число скачиваний, остаток, дата и статус. Ссылка перестаёт действовать
после исчерпания максимума (410); HEAD-запрос скачивание не расходует.
Отдельную ссылку можно удалить.
6.8.4. TTL, объём и очистка
store.default-ttl-days— TTL по умолчанию (0 — вечно);store.max-ttl-days— максимум TTL (по умолчанию 3650);store.max-size— предел одного файла (по умолчанию 1 ГиБ, абсолютный предел 8 ГиБ);store.max-total-bytes— предел суммы размеров (0 — без предела);store.max-objects— предел числа объектов (0 — без предела).
Истёкшие объекты удаляются циклом очистки каждые 10 минут вместе с
файлами и ссылками. В отличие от PKI-публикаций, старые файлы не
вытесняются автоматически: при исчерпании объёма загрузка получает 507.
На узле с небольшим диском задавайте store.max-total-bytes
заметно меньше свободного места.
Файлы лежат в
<state-dir>/store/objects/<xx>/<id> с
правами 0600, метаданные — в таблицах bin_objects и
bin_links той же БД. Бэкап PostgreSQL (pg_dump) сохраняет
метаданные, но не тела файлов: каталог store нужно включить в файловый
бэкап отдельно.
6.8.5. Примеры (Bearer-токен)
# загрузка
curl -fsS -H "Authorization: Bearer $CDROP_TOKEN" \
-F file=@release.apk \
-F description="Release 1.0" \
-F ttl=365 \
https://drop.example/admin/store/upload
# создание ссылки на объект
curl -fsS -H "Authorization: Bearer $CDROP_TOKEN" \
-d object_id=<ID> -d note="for Ivan" -d max=1 \
https://drop.example/admin/store/link
# скачивание по короткой ссылке
curl -fLO https://drop.example/s/<token>Поля description и ttl могут идти до или
после файла. Поле csrf для входа по cookie/Basic обязано
идти до файла; Bearer-токен CSRF не требует. При
X-Requested-With: XMLHttpRequest сервис отвечает
201 {id,url} вместо редиректа — так работает прогресс-бар в
форме.
7. Конфигурация
Формат — YAML или JSON. Путь: -config или
CDROP_CONFIG. Поля — kebab-case (base-url,
max-size, publish-tokens,
web-quota, token-env), как в cnet. Старые
snake_case ключи (base_url, max_size, …)
читаются без изменений; если заданы оба варианта, побеждает
kebab-case.
Пример:
listen: ":80" # HTTP: редирект и ACME
listen-tls: ":443" # HTTPS; пусто - TLS выключен
tls-cert: "/etc/cdrop/tls/fullchain.pem" # полная цепочка
tls-key: "/etc/cdrop/tls/privkey.pem" # закрытый ключ
challenge-dir: "/var/www/certbot" # ACME webroot; пусто - нет
base-url: "https://drop.example" # публичный адрес без "/"base-url: "https://drop.example" # публичный адрес без "/"
# canonical-url: "https://drop.example" # security.txt; пусто - base-url
state-dir: "/var/lib/cdrop" # состояние, права 0700
db: "postgres" # sqlite | postgres
dsn: "host=/var/run/postgresql dbname=cdrop user=cdrop sslmode=disable"
max-size: 16384 # байт; максимум 65536
max-db-bytes: 1073741824 # 1 ГиБ; -1 - без предела
max-objects: 0 # 0 - без предела по числу строк
default-ttl: "24h" # срок по умолчанию
max-ttl: "168h" # максимум 168h
fetches: 3 # 1..3
grants: # ограниченные гранты публикации
enabled: true # false - заголовок X-CDROP-GRANT -> 403
uses: 3 # публикаций по гранту по умолчанию
max-uses: 10 # максимум, который можно запросить
# publish-token: "..." # общий токен; лучше CDROP_TOKEN
# metrics-token: "..." # /metrics; лучше CDROP_METRICS_TOKEN
encrypt-bodies: false # AES-256-GCM; ключ CDROP_DATA_KEY
web-publish: false # форма /publish с captcha
web-quota: # квоты формы на IP за 24 часа
publishes-per-day: 20
bytes-per-day: 1048576
active-objects: 50
rate-limit:
publish: 10 # публикация/метаданные/удаление в минуту
get: 60 # получение в минуту
quotas: # на токен за скользящие 24 часа
publishes-per-day: 500 # 0 - без ограничения
bytes-per-day: 8388608
active-objects: 200
mode: "pki" # pki | store | pki+store
store: # режимы store и pki+store
# dir: "/var/lib/cdrop/store" # пусто - <state-dir>/store
max-size: 1073741824 # один файл; предел 8 ГиБ
max-total-bytes: 0 # сумма; 0 - без предела
max-objects: 0 # 0 - без предела
default-ttl-days: 0 # 0 - вечно
max-ttl-days: 3650 # максимум TTL в днях
publish-tokens:
- token-env: "CDROP_TOKEN_REGISTRAR" # имя переменной с токеном
scopes: ["cnet"] # cnet | pem | ssh
expires: "2027-01-01T00:00:00Z" # RFC3339; пусто - бессрочно
disabled: false # отключить без удаления из env
quota: # переопределение quotas
publishes-per-day: 50
bytes-per-day: 1048576
active-objects: 20
# abuse-contact: "mailto:abuse@example.org" # security.txt; пусто - 404
log:
level: "info" # flood|trace|debug|info|notice|warn|error|crit
pipe: "stderr" # stderr или файл
format: "tint" # tint | logfmt | json
file: "/var/log/cdrop/cdrop.log" # файл с ротацией
file-mode: "0600" # права файла
color-off: true # без ANSI-подкраски
rotate:
enable: true # ротация lumberjack
max-size: 50 # МБ до ротации
max-age: 14 # дней хранить
max-backups: 5 # старых копий
compress: true # gzip старых копий
local-time: true # имена по локальному времени7.1. Переменные окружения
| Переменная | Назначение |
|---|---|
CDROP_CONFIG |
путь к конфигу |
CDROP_TOKEN |
общий publish-токен (любая область) |
CDROP_METRICS_TOKEN |
токен доступа к /metrics |
CDROP_TOKEN_<NAME> |
значения для publish-tokens[].token-env |
Токены в YAML не пишутся. Для systemd — EnvironmentFile
0600.
7.2. Артефакты cnet
Armored-блоки CNET INVITATION,
CNET REQUEST, CNET PASS и другие блоки с
префиксом CNET принимаются по формату armor. Подпись, срок,
сеть и вложенные приглашения не проверяются. kind для трёх
известных типов —
invite/request/passport, для
остальных — cnet; scope — cnet. В
одной публикации допускается один блок cnet.
7.3. Публичные PEM и SSH
Допустимые PEM-блоки:
CERTIFICATE, TRUSTED CERTIFICATE,
PUBLIC KEY, RSA PUBLIC KEY,
CERTIFICATE REQUEST, NEW CERTIFICATE REQUEST,
X509 CRL, PKCS7.
Цепочка сертификатов — это несколько блоков CERTIFICATE
в одной публикации; сервис не проверяет её целостность, только формат и
общий размер.
Запрещены приватные ключи и их контейнеры:
RSA PRIVATE KEY (PKCS#1), PRIVATE KEY
(PKCS#8), ENCRYPTED PRIVATE KEY,
EC PRIVATE KEY, DSA PRIVATE KEY,
OPENSSH PRIVATE KEY, а также произвольный текст. Бинарные
PKCS#12/PFX не принимаются.
SSH: строки authorized_keys вида
<type> <base64> [comment]. Допустимы
ssh-rsa, ssh-ed25519,
ecdsa-sha2-nistp256/384/521,
sk-ssh-ed25519@openssh.com,
sk-ecdsa-sha2-nistp256@openssh.com и их сертификатные формы
-cert-v01@openssh.com. Дополнительно принимаются публичные
ключи ГОСТ 2012 ssh-gost2012-256 и
ssh-gost2012-512 (сертификатных форм у них нет). Строки
# ... — комментарии. Смешивать ключи и сертификаты
нельзя.
Не более 16 блоков или строк SSH на публикацию; общий размер тела
ограничен max-size (16 КиБ по умолчанию, максимум 64
КиБ).
7.4. Пределы
| Параметр | По умолчанию | Диапазон |
|---|---|---|
| Размер | 16 КиБ | до 64 КиБ |
| TTL | 24 ч | 1 ч – 168 ч |
| Получений | 3 | 1–3 |
| Публикаций/удалений | 10/мин на IP | настраивается |
| Получений | 60/мин на IP | настраивается |
| Очистка | 10 мин | фиксировано |
8. Токены доступа
8.1. Принцип
Токен — непрозрачный bearer-секрет. Он не является JWT, не подписывается и не выводится из пароля. Сервис сравнивает его constant-time и не хранит в БД.
Токен даёт право:
- публиковать артефакты;
- удалять их;
- читать метаданные.
Получение по короткой ссылке — публичное по владению ID.
8.2. Формирование
Рекомендуемый способ:
openssl rand -hex 32Это 32 случайных байта (256 бит) в hex, 64 символа. Минимальных требований к формату сервис не накладывает, кроме непустой строки, но случайные 256 бит — разумный минимум.
Установщик scripts/install.sh:
- читает
TOKEN_FILE, если он задан; - иначе генерирует
openssl rand -hex 32; - пишет
/etc/cdrop/cdrop.envс правами0600 root:root; - при
REUSE_TOKEN=1(по умолчанию) не меняет существующий токен.
8.3. Области, квоты, срок
publish-tokens:
- token-env: "CDROP_TOKEN_REGISTRAR"
scopes: ["cnet"]
expires: "2027-01-01T00:00:00Z"
disabled: false
quota:
publishes-per-day: 50
bytes-per-day: 1048576
active-objects: 20scopes—cnet,pemилиssh;expires— RFC3339, после срока токен даёт401;disabled— отключение без удаления из окружения;quota— переопределение общихquotas(нулевые поля наследуются).
Квоты считаются за скользящие 24 часа по
objects.token_id. Проверка квоты и вставка выполняются в
одной транзакции; в PostgreSQL дополнительно берётся advisory lock по
token_id, поэтому параллельные публикации не превышают
лимит.
8.4. Идентификатор для аудита
Сервис вычисляет:
token-id = sha256(token)[:16]
Он попадает в журнал публикации/удаления и в
objects.token_id. Сам токен в журнал и БД не пишется. По
token-id можно разобрать инцидент, не раскрывая секрет.
8.5. Ротация
- Сгенерировать новый токен.
- Добавить второй
publish-tokens(или временно использовать общийCDROP_TOKEN). - Перезапустить сервис (env читается при старте).
- Переключить системы на новый токен.
- Удалить старый токен из env/конфига и перезапустить.
Для аварийного отзыва используется таблица
revoked_tokens (миграция 4), проверяемая на каждом
publish/meta/delete:
cdrop tokenid -token "$TOKEN" # несекретный token-id
cdrop revoke -config cdrop.yaml <id> # отозвать сразу, без рестарта
cdrop revoke -config cdrop.yaml -list
cdrop revoke -config cdrop.yaml -delete <id>disabled: true в конфиге остаётся статическим вариантом
(нужен рестарт).
Для abuse/takedown:
cdrop takedown -config cdrop.yaml <ID> # объект -> 410
cdrop takedown -config cdrop.yaml -token-id <id> # токен -> 401
cdrop takedown -config cdrop.yaml -listПричина по умолчанию abuse (-reason),
записи хранятся в таблице takedowns. Политика —
ABUSE.md. Контакт задаётся abuse-contact и
публикуется в /.well-known/security.txt.
8.6. Ограниченные гранты публикации
Грант — это ссылка на этот же сервис, которая позволяет получателю опубликовать следующий артефакт без publish-токена:
https://drop.example/p/cdg_<случайный токен>
Грант запрашивается при публикации заголовком:
X-CDROP-GRANT: request=3
Формат — <kind> или
<kind>=<uses>. Тип обязан принадлежать области
артефакта:
| Область родителя | Допустимые kind |
|---|---|
| cnet | invite, request, passport,
cnet (обычно request) |
| pem | pem |
| ssh | ssh-key, ssh-cert |
uses — от 1 до grants.max-uses; без
=N берётся grants.uses. Грант не привязан к
cnet и работает для PEM/SSH.
Сервис встраивает полную ссылку (с
base-url) в опубликованный артефакт:
- armor cnet и PEM — заголовком после
-----BEGIN ...-----:X-CDROP-GRANT: https://drop.example/p/cdg_...; - SSH (authorized_keys) — строкой-комментарием:
# X-CDROP-GRANT: https://drop.example/p/cdg_....
Заголовок находится вне подписанных байтов cnet-артефакта и не ломает подпись. Для PEM неизвестные заголовки игнорируются стандартными парсерами; для SSH комментарии стандартны.
Свойства гранта:
- хранится в строке публикации-родителя
(
objects.grant_*); DELETE, takedown, истечение TTL или вытеснение строки уничтожают грант; - в БД хранится только
sha256(token), сам токен — в ссылке; - расход и вставка дочерней публикации — одна транзакция;
- разрешает только свой
kind/scope; чужая область —403; - не даёт метаданных, удаления, панели, captcha-формы;
- не может выдать новый грант:
X-CDROP-GRANTпо токену гранта —403; - истёк или исчерпан —
410 Gone; - в журнале путь
/p/<token>редактируется до/p/<grant>, в записи публикации остаётся grant-id.
Без заголовка X-CDROP-GRANT поведение сервиса не
меняется: старые клиенты и API работают как прежде.
9. Привязка к секретам систем: пример регистратора cnet
Задача: регистратор cnet должен публиковать Приглашения и Паспорта в cdrop, не имея доступа к чужим областям.
9.1. Шаг 1. Токен на стороне cdrop
На сервере cdrop:
umask 077
TOKEN=$(openssl rand -hex 32)
printf 'CDROP_TOKEN_REGISTRAR=%s\n' "$TOKEN" >> /etc/cdrop/cdrop.envВ конфиг cdrop:
publish-tokens:
- token-env: "CDROP_TOKEN_REGISTRAR"
scopes: ["cnet"]
quota:
publishes-per-day: 100
bytes-per-day: 4194304
active-objects: 50Перезапуск:
systemctl restart cdropПроверка, что токен распознан:
set -a; . /etc/cdrop/cdrop.env; set +a
curl -X POST https://drop.example/api/v1/objects \
-H "Authorization: Bearer $CDROP_TOKEN_REGISTRAR" \
-H "X-CDROP-TTL: 1h" \
--data-binary @test-invite.txtЕсли ответ 201 — токен и область настроены.
401 — токен не найден/истёк; 403 — область не
разрешена; 422 — артефакт отклонён по формату (приватный
ключ, недопустимый блок, смешение классов).
9.2. Шаг 2. Передача токена регистратору
Токен — такой же секрет, как ключ. Варианты хранения:
- systemd
EnvironmentFile=/etc/cnet/cdrop.env(0600 root); - файл
0600в каталоге конфигурации регистратора; - Vault/KMS с коротким временем жизни;
- переменная окружения только на время команды.
Нельзя: git, YAML в репозитории, argv (виден в ps),
логи.
9.3. Шаг 3. Публикация из регистратора
CLI берёт токен из CDROP_TOKEN или
-token:
# Приглашение
cnet pki invite new --out invite.txt ...
CDROP_TOKEN="$CDROP_TOKEN_REGISTRAR" \
cdrop publish -config registrar-cdrop.yaml -file invite.txt
# Паспорт
CDROP_TOKEN="$CDROP_TOKEN_REGISTRAR" \
cdrop publish -config registrar-cdrop.yaml -file pass.txt -qrregistrar-cdrop.yaml содержит только адрес:
base-url: "https://drop.example"Токен в нём не хранится.
Альтернатива — отдельный env для CLI:
install -m 0600 /dev/null /etc/cnet/cdrop.env
printf 'CDROP_TOKEN=%s\n' "$REGISTRAR_TOKEN" > /etc/cnet/cdrop.env
set -a; . /etc/cnet/cdrop.env; set +a
cdrop publish -config registrar-cdrop.yaml -file invite.txt9.4. Шаг 4. Получатель
cdrop get -config subscriber-cdrop.yaml <ID>
# или открыть https://drop.example/o/<ID>Получатель проверяет артефакт штатными средствами cnet:
cnet pki invite show --in invite.txt
cnet pki passport verify --in pass.txt --root root.crt ...cdrop не заменяет эту проверку.
9.5. Шаг 5. Аудит и отзыв
В журнале cdrop:
published id=... kind=invite scope=cnet token=<token-id>
По token-id видно, какой системой сделана публикация.
Для отзыва:
publish-tokens:
- token-env: "CDROP_TOKEN_REGISTRAR"
disabled: trueи рестарт. Либо удалить строку из env.
10. Интеграция
10.1. CLI
cdrop serve -config /etc/cdrop/cdrop.yaml
cdrop check -config /etc/cdrop/cdrop.yaml [-db]
cdrop publish -config cdrop.yaml -file invite.txt [-ttl 24h] [-qr] [-qr-out png]
cdrop publish -config cdrop.yaml -file invite.txt -grant request=3
cdrop get -config cdrop.yaml <ID|URL>
cdrop status -config cdrop.yaml <ID|URL>
cdrop qr -config cdrop.yaml [-out png] <ID|URL>
cdrop version-qr печатает QR в консоль,
-qr-out/-out пишут PNG.
10.2. cnet pki
Сейчас:
cnet pki invite new ... --out invite.txt
cdrop publish ... -file invite.txtВ планах — cnet pki invite new --publish и
passport show --publish, чтобы публикация была одной
командой.
10.3. Android
Издатель cnet может получить режим «поделиться приглашением через
cdrop». Требуется разрешение INTERNET и явное включение
cdrop; при выключенном режиме сетевых вызовов нет.
10.4. Другие системы
Любая система с токеном может публиковать через HTTP:
curl -X POST https://drop.example/api/v1/objects \
-H "Authorization: Bearer $TOKEN" \
-H "X-CDROP-TTL: 12h" \
-H "X-CDROP-GRANT: request=3" \
--data-binary @artifact.txtОтвет содержит grant.url — полную ссылку для получателя.
Для публикации по гранту:
curl -X POST https://drop.example/api/v1/objects \
-H "Authorization: Bearer cdg_..." \
--data-binary @request.txtДля scoped-токена укажите его в Authorization; область
токена должна совпадать с scope артефакта
(cnet, pem, ssh).
11. Развёртывание
11.1. Требования
- Linux с systemd (проверено на Debian 13);
- исходящий доступ для Let’s Encrypt (при TLS_MODE=letsencrypt);
- PostgreSQL 17 или SQLite;
- открытые 80/443 (или другие порты);
- sudo без пароля для установщика (или вход root).
11.2. Установщик
cd cdrop
cp scripts/install.env.example scripts/install.env
$EDITOR scripts/install.env
./scripts/install.sh scripts/install.envinstall.env в git не попадает.
Основные поля:
SSH_HOST="drop.example"
DOMAIN="drop.example"
BASE_URL="https://drop.example"
DB="postgres"
TLS_MODE="letsencrypt" # letsencrypt|files|off
TOKEN_FILE="" # пусто - сгенерировать
REUSE_TOKEN="1"Что делает скрипт:
- собирает исполняемый файл (
CGO_ENABLED=0,-trimpath -s -w); - готовит конфиг, env, unit, hook, cron, бэкап;
- проверяет окружение (preflight) до остановки сервиса;
- копирует файлы по ssh/scp;
- создаёт пользователя, каталоги, роль/БД PostgreSQL;
- выпускает сертификат (standalone при первом запуске);
- запускает сервис и проверяет
/healthz; - при неудаче откатывает исполняемый файл из
cdrop.prev.
11.3. TLS
letsencrypt— certbot, webroot/var/www/certbotдля продления;files— готовыеfullchain.pem/privkey.pem;off— только HTTP (для отладки, не для публичного узла).
Продление: /etc/cron.d/cdrop-certbot дважды в сутки,
deploy-hook копирует сертификат и делает
systemctl reload cdrop (SIGHUP, без простоя).
11.4. PostgreSQL
Установщик создаёт роль и БД через peer-auth:
su - postgres -c 'createuser --login cdrop'
su - postgres -c 'createdb -O cdrop cdrop'DSN:
host=/var/run/postgresql dbname=cdrop user=cdrop sslmode=disable
Схема и миграции создаются при старте: таблица
schema_migrations хранит версии 1–6 (базовая схема,
network → scope, token_id,
revoked_tokens). Миграция переименования необратима.
Установщик сам делает pg_dump в
/var/backups/cdrop/pre-upgrade-*.dump перед
обновлением.
11.5. Проверка после установки
curl -I http://drop.example/ # 301 на https
curl https://drop.example/healthz # ok
curl --resolve drop.example:443:127.0.0.1 \
https://drop.example/metrics # локально
cdrop check -config /etc/cdrop/cdrop.yaml
# -db требует peer-auth: запускать от пользователя cdrop
su - cdrop -s /bin/sh -c \
'cdrop check -db -config /etc/cdrop/cdrop.yaml'
systemctl status cdrop11.6. Обновление и откат
Повторный install.sh идемпотентен: конфиг/unit
обновляются, токен сохраняется, исполняемый файл заменяется. При
неудачном health-check автоматически возвращается
cdrop.prev.
Установщик сохраняет cdrop.prev,
cdrop.yaml.prev и cdrop.service.prev; при
неудачном health-check возвращаются все три. Ручной откат:
install -m 0755 /usr/local/bin/cdrop.prev /usr/local/bin/cdrop
install -m 0644 /etc/cdrop/cdrop.yaml.prev /etc/cdrop/cdrop.yaml
install -m 0644 /etc/systemd/system/cdrop.service.prev \
/etc/systemd/system/cdrop.service
systemctl daemon-reload
systemctl restart cdrop11.7. Бэкап и восстановление
Ежедневно /usr/local/sbin/cdrop-backup делает
pg_dump в /var/backups/cdrop, хранит 7 суток.
Раз в неделю /usr/local/sbin/cdrop-backup-verify
восстанавливает последний дамп во временную БД, проверяет таблицу
objects и удаляет её.
Восстановление:
su - cdrop -s /bin/sh -c \
'pg_restore --clean --no-owner -d cdrop /var/backups/cdrop/<файл>'
systemctl restart cdrop11.8. Релизы
make -C cdrop distСобираются linux/amd64 и linux/arm64:
dist/cdrop-<version>-linux-amd64.tar.gz
dist/cdrop-<version>-linux-arm64.tar.gz
dist/*.sha256
В архив входят исполняемый файл, config.example.yaml,
deploy/, установщик, healthcheck, SPEC, README, руководство
(Markdown RU/EN, PDF и self-contained HTML), OpenAPI и
BUILD.txt (version/git/built_at/os/arch). Проверка:
sha256sum -c.
Исходники руководства — cdrop-manual-ru.md и
cdrop-manual-en.md; make -C cdrop manual
собирает PDF и HTML. HTML встраивается в исполняемый файл и доступен по
/cdrop/manual и /ru/cdrop/manual; pandoc на
VPS не требуется.
11.9. Non-root установка и TUI
Матрица 3×3: режим установки × TLS.
| Режим | systemd | Пути | Порты | TLS |
|---|---|---|---|---|
| root | системный | /etc/cdrop, /var/lib/cdrop |
80/443 | letsencrypt/files/off |
| user | systemd --user + linger |
~/.config/cdrop, ~/.local/state/cdrop |
8080/8443 | files/off |
| portable | нет | ~/.cdrop |
8080/8443 | files/off |
root:scripts/install.sh(по ssh/scp).user:scripts/install-local.sh,MODE=user;loginctl enable-linger $USER— чтобы сервис жил после выхода.portable:MODE=portable,run.sh/stop.sh, без systemd.- Пользовательский сервис не может занять порты <1024: используйте высокие порты и реверс-прокси.
letsencryptдоступен только root-режиму.- TUI:
scripts/install-tui.sh, диалоги черезcdialogизbin/релизного архива или системныйdialog. - Публичный узел — ровно один экземпляр сервиса на порту 443;
дополнительные экземпляры не используются. Порт 80 принадлежит тому же
процессу: редирект на HTTPS и ACME webroot. Продление сертификата
выполняет deploy-hook
cdrop-cert-hookи reload сервиса.
11.10. Удаление
Автоматического uninstall нет. Порядок:
systemctl disable --now cdrop
rm -f /etc/systemd/system/cdrop.service
rm -f /etc/cron.d/cdrop-certbot /etc/cron.d/cdrop-backup
rm -f /usr/local/bin/cdrop /usr/local/bin/cdrop.prev
rm -f /usr/local/sbin/cdrop-cert-hook /usr/local/sbin/cdrop-backup
rm -rf /etc/cdrop /var/lib/cdrop
userdel cdrop
su - postgres -c 'dropdb cdrop'
su - postgres -c 'dropuser cdrop'
systemctl daemon-reload12. Эксплуатация
12.1. Журнал
По умолчанию журнал пишется в файл с ротацией (lumberjack), а также в stderr/journald. Пути:
- root:
/var/log/cdrop/cdrop.log; - user/portable:
~/.local/state/<service>/logs/cdrop.log.
Параметры по умолчанию: 50 МБ, 5 копий, 14 дней, gzip,
0600. Это защищает VPS от переполнения.
color-off включён.
В журнал пишутся:
published/fetched/deletedс id, kind, scope, size, sha256, token-id;trimпри вытеснении старых записей;requestс method/path/status/duration;- ошибки проверки — локально, без тела артефакта.
Тела артефактов и токены не логируются.
12.2. Метрики
/metrics (loopback или
CDROP_METRICS_TOKEN):
cdrop_published_total{kind};cdrop_fetched_total{kind};cdrop_gone_total;cdrop_rejected_total;cdrop_deleted_total;cdrop_quota_rejected_total;cdrop_active-objects;cdrop_requests_total{method,status};cdrop_request_duration_seconds(гистограмма);cdrop_tls-cert_expires_seconds/cdrop_tls-cert_expires_in_seconds;cdrop_build_info{version}.
Каждый HTTP-запрос ограничен 10 секундами, включая операции с БД. В
ответах возвращается X-Request-ID (входящий или
сгенерированный), он же пишется в журнал. PostgreSQL использует пул 10/5
с lifetime 30 минут и idle 5 минут.
12.3. Healthcheck и алерты
/usr/local/sbin/cdrop-healthcheck запускается cron
каждые 5 минут:
- проверяет
/healthzи/readyz; - проверяет срок TLS-сертификата (порог
CDROP_CERT_WARN_DAYS, по умолчанию 14 дней); - при сбое пишет в journald и, если задан
CDROP_ALERT_WEBHOOK, отправляет JSON в webhook; - завершается с кодом 1.
Правила Prometheus — deploy/alerts.yml: down, not ready,
5xx, 429, квота, сертификат < 14 и < 3 дней.
За реверс-прокси loopback-проверка бесполезна: запросы с
X-Forwarded-For/Forwarded не считаются
loopback. Задайте CDROP_METRICS_TOKEN или отдельный
listener.
12.4. Ресурсы
Сервис нетребователен: один процесс, SQLite/PostgreSQL, статические
страницы. Основные потребители — БД и TLS. При росте нагрузки следует
смотреть на пул PostgreSQL и индексы
token_id/created_at.
13. Безопасность
- Публикация через API/CLI, удаление и метаданные — по токену.
- Веб-форма
/publish(если включена) допускает анонимную публикацию после captcha; действуют лимиты и квоты на IP. - Токены сравниваются constant-time, не логируются.
- Области и квоты ограничивают ущерб от утечки токена.
- Ошибки
400/422/403обезличены. /metricsзакрыт, метаданные требуют токен.- Сервис запрещает приватные ключи и не предназначен для секретов; подписи и цепочки не проверяются.
- TLS обязателен; HSTS, no-store, noindex, no-referrer, CSP.
- Тело отдаётся как
text/plain/HTML с экранированием;nosniff. - Лимиты: TTL, размер, число получений, rate limit, квоты.
- systemd: отдельный пользователь,
StateDirectory,CAP_NET_BIND_SERVICE,ProtectSystem=strict,PrivateTmp,PrivateDevices,ProtectClock/KernelLogs/Hostname,RestrictNamespaces/Realtime/SUIDSGID,UMask=0077,PrivateMounts,ProtectProc=invisible,ProcSubset=pid,RemoveIPC,SystemCallFilter=@system-service. - Три получения — best-effort: получатель может сохранить текст.
- ID — 60 бит; перебор ограничен rate limit.
14. API-контракт
cdrop/openapi.yaml описывает все публичные эндпоинты,
схемы PublishResponse, Metadata,
Error, параметр ID и Bearer-токен. Корректность YAML
проверяется тестом server/openapi_test.go.
15. Тесты и качество
make -C cdrop check # vet + check-core + test
make -C cdrop cover # покрытие
make -C cdrop fuzz # короткие fuzz-прогоныПокрытие (0.22.0):
| Пакет | Покрытие |
|---|---|
cmd/cdrop |
73% |
store |
82% |
artifact |
95% |
internal/config |
91% |
server |
88% |
secret |
66% |
Интеграционный набор: make -C cdrop test-integration
(install, bwrap-E2E, user-E2E, portable-E2E,
go test ./...).
Генерация установщика проверяется
make -C cdrop test-install (dry-run, конфиг, remote.sh,
права токена). Полный E2E в bwrap — make -C cdrop test-e2e:
раскладка файлов, повторная установка, сохранение токена и rollback при
сбое restart (systemd/PostgreSQL подменяются заглушками).
PostgreSQL-тесты включаются переменной
CDROP_TEST_POSTGRES_DSN. Fuzz: валидатор
artifact и NormalizeID.
16. Ограничения и развитие
Ограничения:
- не CA и не проверяет артефакт за получателя;
- не для секретов и приватных ключей;
- лимит получений не гарантирует конфиденциальность;
- квоты best-effort;
- при переполнении БД вытесняются самые старые активные публикации;
- подписи и цепочки не проверяются;
- один экземпляр сервиса на узел.
Ближайшие направления:
- алерты по 5xx/429/БД/сертификату;
- E2E установщика в контейнере;
- интеграции
cnet pki --publishи Android; - метрики по классам артефактов.
17. Приложения
Разделы 17.2 и 17.3 оформлены как контрольные листы: отметка
[x] означает выполненную проверку. Это перечни контрольных
проверок, а не технологические карты: операции, режимы и нормы времени
здесь не описываются.
17.1. Минимальный конфиг
listen: ":8080"
base-url: "http://127.0.0.1:8080"
state-dir: "./state"
db: "sqlite"
publish-token: "dev-token"
fetches: 3
log:
level: "info"
pipe: "stderr"
format: "logfmt"