refactor: сертификат и ключ в каталоге secrets/, один параметр на каталог
Tests / frontend-test (pull_request) Successful in 3m14s
Tests / frontend-test (pull_request) Successful in 3m14s
Локально путь к сертификату вёл в ~/Downloads у каждого свой, а в контейнере задавался двумя отдельными переменными. Теперь каталог один и подменяется целиком через SECRETS_DIR: по умолчанию secrets/ в корне репозитория, в контейнере /certs (смонтированный каталог с хоста, GRAPH_CERT_DIR не меняется). Имена файлов внутри фиксированы, поэтому GRAPH_CERT_PATH и GRAPH_KEY_PATH больше не нужны. Содержимое secrets/ в .gitignore, кроме README. Отсутствие файлов теперь даёт понятную ошибку вместо ENOENT — на свежем клоне это основной сценарий отказа. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,3 +15,8 @@ yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
.env
|
||||
|
||||
# Сертификаты и ключи. Каталог подменяется целиком: локально это secrets/,
|
||||
# в контейнере — примонтированный /certs (см. docker-compose.yml).
|
||||
secrets/*
|
||||
!secrets/README.md
|
||||
|
||||
@@ -32,10 +32,10 @@ npm run dev
|
||||
так же, как nginx в проде (`nginx.conf`). Без запущенного `[auth]` отправка
|
||||
формы отдаёт 500 от прокси.
|
||||
|
||||
Сервису нужен `server/.env` с сертификатом Azure AD — скопируйте
|
||||
[server/.env.example](server/.env.example) и подставьте свои пути. Сами
|
||||
`.pem`-файлы в git не хранятся, их надо взять у администратора. Подробнее —
|
||||
в [server/README.md](server/README.md).
|
||||
Сервису нужны две вещи: `server/.env` (скопируйте
|
||||
[server/.env.example](server/.env.example)) и сертификат Azure AD в каталоге
|
||||
[`secrets/`](secrets/README.md). Сами `.pem`-файлы в git не хранятся — их надо
|
||||
взять у администратора. Подробнее — в [server/README.md](server/README.md).
|
||||
|
||||
Прочие команды:
|
||||
|
||||
|
||||
+3
-2
@@ -13,8 +13,9 @@ services:
|
||||
environment:
|
||||
GRAPH_TENANT_ID: ${GRAPH_TENANT_ID:?в .env на сервере не задан GRAPH_TENANT_ID}
|
||||
GRAPH_CLIENT_ID: ${GRAPH_CLIENT_ID:?в .env на сервере не задан GRAPH_CLIENT_ID}
|
||||
GRAPH_CERT_PATH: /certs/SPFxDeploy_cert.pem
|
||||
GRAPH_KEY_PATH: /certs/SPFxDeploy_key.pem
|
||||
# Один параметр на весь каталог с секретами — имена файлов внутри
|
||||
# фиксированы (см. server/src/config.ts).
|
||||
SECRETS_DIR: /certs
|
||||
volumes:
|
||||
# Каталог с сертификатом/ключом на сервере — путь задаётся в .env
|
||||
# (GRAPH_CERT_DIR), сами файлы в git не попадают.
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# secrets
|
||||
|
||||
Сертификат и приватный ключ приложения Azure AD, под которым сервис
|
||||
`hr-appeals-auth` получает app-only токен. **В git не попадают** — всё
|
||||
содержимое каталога, кроме этого файла, в `.gitignore`.
|
||||
|
||||
Положите сюда два файла (взять у администратора):
|
||||
|
||||
```
|
||||
secrets/
|
||||
SPFxDeploy_cert.pem
|
||||
SPFxDeploy_key.pem
|
||||
```
|
||||
|
||||
Имена фиксированные — их ждёт [server/src/config.ts](../server/src/config.ts).
|
||||
|
||||
## Подменить каталог целиком
|
||||
|
||||
Путь задаётся одной переменной `SECRETS_DIR`, поэтому каталог можно заменить
|
||||
не трогая код:
|
||||
|
||||
```bash
|
||||
SECRETS_DIR=/path/to/other/secrets npm run dev --prefix server
|
||||
```
|
||||
|
||||
По умолчанию — этот каталог. В продовом контейнере `SECRETS_DIR=/certs`, куда
|
||||
смонтирован каталог с хоста: путь на хосте задаёт `GRAPH_CERT_DIR` в `.env`
|
||||
рядом с `docker-compose.yml` на сервере.
|
||||
|
||||
## Если ключ утёк
|
||||
|
||||
Сертификат перевыпускается в Azure AD (App registrations → нужное приложение →
|
||||
Certificates & secrets), старый оттуда удаляется. Замена файлов здесь без этого
|
||||
ничего не даёт: Azure AD продолжит принимать подпись старым ключом.
|
||||
+6
-6
@@ -1,13 +1,13 @@
|
||||
# Локальный запуск: скопируйте в server/.env и подставьте свои пути к сертификату.
|
||||
# На проде эти переменные задаёт docker-compose (см. docker-compose.yml), .env там свой.
|
||||
# Локальный запуск: скопируйте в server/.env.
|
||||
# На проде эти переменные задаёт docker-compose (см. docker-compose.yml).
|
||||
|
||||
GRAPH_TENANT_ID=b4c2af35-bfd6-4cff-9af9-556efb53e65a
|
||||
GRAPH_CLIENT_ID=26d2e058-0b27-4a00-b49f-1f17c4eecd1d
|
||||
|
||||
# Сертификат и приватный ключ приложения — в git не хранятся, взять у администратора.
|
||||
GRAPH_CERT_PATH=C:/path/to/SPFxDeploy_cert.pem
|
||||
GRAPH_KEY_PATH=C:/path/to/SPFxDeploy_key.pem
|
||||
|
||||
# 3000 занят vite (см. vite.config.ts), поэтому локально сервис слушает 3100 —
|
||||
# именно туда проксирует /api из dev-сервера.
|
||||
PORT=3100
|
||||
|
||||
# Каталог с сертификатом и ключом. По умолчанию — secrets/ в корне репозитория,
|
||||
# задавать нужно только чтобы подменить его на другой (см. secrets/README.md).
|
||||
# SECRETS_DIR=/path/to/secrets
|
||||
|
||||
+5
-3
@@ -13,8 +13,7 @@ assertion) и используется для авторизованных за
|
||||
|---|---|
|
||||
| `GRAPH_TENANT_ID` | Tenant ID Azure AD |
|
||||
| `GRAPH_CLIENT_ID` | Client ID App Registration, к которому привязан сертификат `SPFxDeploy` |
|
||||
| `GRAPH_CERT_PATH` | Путь к `SPFxDeploy_cert.pem` внутри контейнера (см. `docker-compose.yml` в корне репозитория) |
|
||||
| `GRAPH_KEY_PATH` | Путь к `SPFxDeploy_key.pem` внутри контейнера |
|
||||
| `SECRETS_DIR` | Каталог с `SPFxDeploy_cert.pem` и `SPFxDeploy_key.pem` (имена файлов фиксированы). По умолчанию — [`secrets/`](../secrets/README.md) в корне репозитория; в контейнере `/certs`, куда смонтирован каталог с хоста. Задавать нужно только чтобы подменить каталог целиком |
|
||||
| `GRAPH_SCOPE` | OAuth-scope запрашиваемого токена. По умолчанию — App ID URI самого `api.tnsu.kz` (`api://9232d9e2-.../.default`), задавать не нужно. Токен для `https://graph.microsoft.com/.default` тут не подойдёт: `api.tnsu.kz` проверяет `aud` входящего JWT и отвечает 401 |
|
||||
| `PORT` | Порт HTTP-сервера сервиса, по умолчанию `3000` (локально — `3100`, см. `.env.example`) |
|
||||
|
||||
@@ -23,10 +22,13 @@ assertion) и используется для авторизованных за
|
||||
Из корня репозитория `npm run dev` поднимает сервис вместе с фронтендом. Отдельно:
|
||||
|
||||
```bash
|
||||
cp .env.example .env # подставить свои пути к SPFxDeploy_cert.pem / _key.pem
|
||||
cp .env.example .env # tenant/client id и порт
|
||||
npm run dev # tsc + запуск с чтением .env (--env-file, Node 20+)
|
||||
```
|
||||
|
||||
Сертификат и ключ при этом должны лежать в [`secrets/`](../secrets/README.md) —
|
||||
в git они не хранятся.
|
||||
|
||||
Проверить, что авторизация работает: `curl http://localhost:3100/diag/token` →
|
||||
`{"ok":true}`. Если вернулась ошибка — в ней будет ответ Azure AD целиком.
|
||||
|
||||
|
||||
+14
-2
@@ -1,3 +1,5 @@
|
||||
import { join, resolve } from 'node:path';
|
||||
|
||||
function requireEnv(name: string): string {
|
||||
const value = process.env[name];
|
||||
if (!value) {
|
||||
@@ -6,11 +8,21 @@ function requireEnv(name: string): string {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Каталог с сертификатом и приватным ключом приложения. Одна переменная на весь
|
||||
* каталог, а не путь к каждому файлу: так его можно подменить централизованно —
|
||||
* локально это `secrets/` в корне репозитория (в git не попадает), в контейнере
|
||||
* SECRETS_DIR=/certs, куда смонтирован каталог с хоста (см. docker-compose.yml).
|
||||
*
|
||||
* __dirname здесь — server/dist, поэтому до корня репозитория два уровня вверх.
|
||||
*/
|
||||
const secretsDir = process.env.SECRETS_DIR ?? resolve(__dirname, '../../secrets');
|
||||
|
||||
export const config = {
|
||||
tenantId: requireEnv('GRAPH_TENANT_ID'),
|
||||
clientId: requireEnv('GRAPH_CLIENT_ID'),
|
||||
certPath: requireEnv('GRAPH_CERT_PATH'),
|
||||
keyPath: requireEnv('GRAPH_KEY_PATH'),
|
||||
certPath: join(secretsDir, 'SPFxDeploy_cert.pem'),
|
||||
keyPath: join(secretsDir, 'SPFxDeploy_key.pem'),
|
||||
// Токен нужен не для Graph напрямую, а для api.tnsu.kz — он сам ходит в
|
||||
// SharePoint. Это его App ID URI: с graph.microsoft.com/.default api вернёт
|
||||
// 401, потому что проверяет aud во входящем JWT.
|
||||
|
||||
+17
-2
@@ -21,9 +21,24 @@ const REFRESH_MARGIN_MS = 60_000;
|
||||
|
||||
let cached: CachedToken | null = null;
|
||||
|
||||
function readSecret(path: string): string {
|
||||
try {
|
||||
return readFileSync(path, 'utf8');
|
||||
} catch (cause) {
|
||||
// Самая частая причина на свежем клоне — файлов просто нет: в git они не
|
||||
// хранятся. Ошибка ENOENT сама по себе не подсказывает, что делать.
|
||||
throw new Error(
|
||||
`Не удалось прочитать ${path}. Сертификат и ключ в git не хранятся — ` +
|
||||
'положите их в каталог secrets/ (см. secrets/README.md) или укажите ' +
|
||||
'другой каталог через SECRETS_DIR.',
|
||||
{ cause }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function buildClientAssertion(): string {
|
||||
const certificatePem = readFileSync(config.certPath, 'utf8');
|
||||
const privateKey = readFileSync(config.keyPath, 'utf8');
|
||||
const certificatePem = readSecret(config.certPath);
|
||||
const privateKey = readSecret(config.keyPath);
|
||||
|
||||
// x5t — SHA-1 отпечаток DER-сертификата, base64url. По нему Azure AD находит
|
||||
// публичный ключ приложения и проверяет подпись JWT.
|
||||
|
||||
Reference in New Issue
Block a user