Аутентификация API и TLS
Узел защищает свои владельческие поверхности с помощью HTTP Basic по секретному файлу на диске. Кошелёк защищает свой Owner API аналогично, по собственному секретному файлу, а Owner API v3 дополнительно требует токен, полученный с паролем кошелька. Внешние поверхности не требуют учётных данных, поскольку интерактивный перевод требует, чтобы принимающая сторона была доступна для стороннего участника. Терминация TLS остаётся на усмотрение оператора.
Поверхности
| Поверхность | Порт по умолчанию | Учётные данные | Кто должен вызывать |
|---|---|---|---|
Узел /v1/* | 3413 | Basic, api_secret_path, на каждом маршруте кроме GET /v1/version | Вы, с той же машины |
Узел /v2/owner | 3413 | Basic, api_secret_path. Один простой вызов get_status принимается без него | Вы, с той же машины |
Узел /v2/foreign | 3413 | Опциональный Basic, foreign_api_secret_path | Всё, что читает цепочку |
Owner /v3/owner кошелька | 3420 | Basic, api_secret_path кошелька, и токен из open_wallet внутри зашифрованного конверта | Вы, с той же машины |
Owner /v2/owner кошелька | 3420 | Basic, api_secret_path кошелька. Заменён на /v3/owner | Ничего. Привяжите слушатель к loopback |
Foreign /v2/foreign кошелька | 3415 | Нет | Контрагент, оплачивающий вам |
Foreign API кошелька принимает слейт (Slate), возвращает частично подписанный слейт, формирует coinbase для майнера, настроенного на выплату в этот кошелёк, и сообщает свою версию. Ограничьте доступ к нему на уровне прокси.
Учётные данные /v1 узла распространяются на весь префикс, включая операции чтения цепочки, открытые
на /v2/foreign. Единственное исключение — точный запрос GET /v1/version, который отвечает всем; POST к тому же пути
по-прежнему требует учётных данных. См. поверхность REST v1.
api_secret_path указывает на разные файлы в конфигурации каждого бинарника. Обе поверхности узла читают файл
узла — .api_secret. Файл кошелька называется .owner_api_secret и защищает собственный слушатель Owner кошелька.
Доступ владельца к кошельку
/v3/owner имеет два уровня защиты, и вызов должен пройти оба.
Первый — HTTP Basic на слушателе. Имя пользователя epic, пароль — содержимое .owner_api_secret кошелька, realm
EpicOwnerAPI. Он охватывает всё на слушателе Owner, кроме /v2/foreign, то есть /v3/owner и /v2/owner одинаково, и проверяется
до выполнения любого метода (controller/src/controller.rs:143). Закомментируйте api_secret_path кошелька — и этот уровень не
устанавливается.
Второй — токен. Каждый метод в трейте v3 принимает его (api/src/owner_rpc_s.rs:78), а получение токена означает
завершение ECDH-рукопожатия и последующий вызов open_wallet с паролем кошелька. См. зашифрованное
рукопожатие.
Первый вызов устанавливает общий ключ. Он несёт только учётные данные Basic и ничего более:
curl -s -u epic:$(cat ~/.epic/main/.owner_api_secret) \
-d '{"jsonrpc":"2.0","method":"init_secure_api","params":{"ecdh_pubkey":"<your compressed public key, hex>"},"id":1}' \
http://127.0.0.1:3420/v3/owner
Без -u слушатель отвечает 401 до начала рукопожатия. Замените ~/.epic/main на каталог кошелька, если
кошелёк находится в другом месте.
Помимо этих учётных данных, каждый метод на /v3/owner принимает token, а токен получают через open_wallet,
которому требуется пароль кошелька. owner_api_interface по умолчанию равен 127.0.0.1; для удалённого доступа используйте
туннель до loopback. Слушатель — controller/src/controller.rs:123.
Полный список методов, зашифрованный конверт и информация о том, какие вызовы перемещают средства, находятся на странице wallet Owner API.
Учётные данные узла
Префикс /v1 узла и /v2/owner используют HTTP Basic с именем пользователя буквально epic и содержимым
секретного файла в качестве пароля:
Authorization: Basic base64("epic:" + <secret file contents>)
Узел создаёт ~/.epic/<network>/.api_secret при первом запуске и указывает api_secret_path на него, поэтому это работает без настройки:
curl -u epic:$(cat ~/.epic/main/.api_secret) \
-d '{"jsonrpc":"2.0","method":"get_status","params":[],"id":1}' \
http://127.0.0.1:3413/v2/owner
Секретные файлы содержат одну 20-символьную случайную буквенно-цифровую строку без завершающего
символа новой строки (config/src/config.rs:84), а сравнение выполняется за постоянное время (basic_auth_matches()). Realm — EpicAPI на
поверхности owner и EpicForeignAPI на foreign. Отклонённый вызов, на любом бинарнике, возвращает 401 с
заголовком WWW-Authenticate и телом ошибки JSON-RPC:
{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Unauthorized"}, "id": null}
Два вызова обрабатываются без учётных данных:
GET /v1/version— именно этот метод и путь (api/src/handlers.rs:118).- Один
get_statusна/v2/owner, отправленный отдельно, а не в пакете, сparamsравным[]илиnullи телом не более 4 КиБ. Возвращает тот же статус, что и аутентифицированный вызов. Любой другой метод, пакет или тело, которое не парсится, получает 401 (is_legacy_status_request()).
Остальное поведение следует из принципа отказа узла в закрытом состоянии:
- Если
api_secret_pathзакомментирован или указывает на несуществующий файл, секрета для сравнения нет, поэтому каждый защищённый вызов возвращает 401 всем, с-uили без. Отвечают только два вызова, описанных выше. Комментирование ключа блокирует/v1и/v2/owner; оно не открывает их. - Пустой
.api_secretперегенерируется с новым значением, а не читается как «нет секрета», поэтому очистка файла не отключает аутентификацию (config/src/config.rs:96). OPTIONSпропускается до проверки учётных данных, что обеспечивает работу CORS preflight.
Опциональные учётные данные на внешней поверхности узла
foreign_api_secret_path заставляет /v2/foreign узла также требовать учётные данные. Создайте секрет самостоятельно и укажите
абсолютный путь:
head -c 40 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c 20 > ~/.epic/main/.foreign_api_secret
[server]
foreign_api_secret_path = "/home/you/.epic/main/.foreign_api_secret"
Realm — EpicForeignAPI, имя пользователя по-прежнему epic. Проверьте с помощью:
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1:3413/v2/foreign
# 401 после установки секрета
Кошелёк никогда не отправляет учётные данные на /v2/foreign узла, что бы ни было в его собственном .api_secret,
поэтому каждое выполняемое им чтение цепочки получает 401 и останавливается с Unauthorized (HTTP 401). Ошибка
предлагает проверить node_api_secret_path, что не поможет. На любом узле, с которым взаимодействует кошелёк, не
создавайте файл, на который указывает foreign_api_secret_path (ключ установлен по умолчанию и безвреден при отсутствии
файла), или закомментируйте ключ, а ограничение поверхности foreign реализуйте на уровне прокси.
Учётные данные, которые кошелёк передаёт узлу
node_api_secret_path указывает в другую сторону: это секрет узла, который хранит кошелёк, и по умолчанию он равен
<wallet dir>/.api_secret. Кошелёк отправляет его только при вызовах /v2/owner, кроме get_status, и никогда — на /v2/foreign или /v2/tor
(impls/src/node_clients/http.rs:115).
На практике это один вызов, get_onion_addresses, который кошелёк выполняет при финализации перевода через epicbox,
чтобы опубликовать транзакцию через Tor. Отсутствующий или несовпадающий секрет приводит к тому, что
этот запрос возвращает пустой результат, и кошелёк публикует транзакцию обычным способом. Всё
остальное, что кошелёк запрашивает у узла — балансы, выходы (outputs), ядра (kernels), публикацию
транзакции и проверку синхронизации — направляется на эндпоинты, не требующие учётных данных.
Проверка синхронизации использует Foreign API ready_for_txs, и только узел, у которого этот метод
отсутствует, возвращает вместо него неаутентифицированный get_status.
Поэтому кошелёк, у которого .api_secret отличается от секрета узла, всё равно работает. Ответ 401 от узла
указывает на другое: на foreign_api_secret_path на узле, описанный выше, или на узел старше 4.1.0. Второй случай
прочитан из кода, а не проверен на практике: узел 4.0.x с секретом помещает весь /v2/owner за Basic auth
и поэтому отклоняет неаутентифицированный get_status от кошелька.
TLS
Оба бинарника принимают одну и ту же пару, по умолчанию закомментированную:
tls_certificate_file = "/path/to/fullchain.pem"
tls_certificate_key = "/path/to/privkey.pem"
Указание сертификата переключает слушатель с HTTP на HTTPS. Указание сертификата без ключа является ошибкой при запуске. Сертификаты читаются как PEM-цепочка; ключи могут быть в формате PKCS#1, PKCS#8 или SEC1 PEM. Файл, который не удаётся открыть, разобрать или который не соответствует своему сертификату, останавливает процесс вместо отката к HTTP, поэтому неверная пара явно завершается ошибкой при запуске.
Встроенные подкоманды epic client обращаются к узлу через http:// (src/bin/cmd/client.rs:160). Используйте curl для работы с
TLS-слушателем.