Add docker admin bootstrap for clean release
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Offline / air-gapped compose profile for enterprise clean release.
|
||||
|
||||
BACKEND_IMAGE=ss-tools-backend:v1.0.0-rc2
|
||||
FRONTEND_IMAGE=ss-tools-frontend:v1.0.0-rc2
|
||||
BACKEND_IMAGE=ss-tools-backend:v1.0.0-rc2-docker
|
||||
FRONTEND_IMAGE=ss-tools-frontend:v1.0.0-rc2-docker
|
||||
POSTGRES_IMAGE=postgres:16-alpine
|
||||
|
||||
POSTGRES_DB=ss_tools
|
||||
@@ -17,5 +17,11 @@ TASK_LOG_LEVEL=INFO
|
||||
|
||||
STORAGE_ROOT=./storage
|
||||
|
||||
# Initial admin bootstrap. Set to true only for the first startup in a new environment.
|
||||
INITIAL_ADMIN_CREATE=false
|
||||
INITIAL_ADMIN_USERNAME=admin
|
||||
INITIAL_ADMIN_PASSWORD=change-me
|
||||
INITIAL_ADMIN_EMAIL=
|
||||
|
||||
OPENAI_API_KEY=
|
||||
ANTHROPIC_API_KEY=
|
||||
|
||||
19
README.md
19
README.md
@@ -250,21 +250,32 @@ cd /home/busya/dev/ss-tools
|
||||
|
||||
```bash
|
||||
# 1. Собрать образы в подключённом контуре
|
||||
./scripts/build_offline_docker_bundle.sh v1.0.0-rc2
|
||||
./scripts/build_offline_docker_bundle.sh v1.0.0-rc2-docker
|
||||
|
||||
# 2. Передать dist/docker/* в изолированный контур
|
||||
# 3. Импортировать образы локально
|
||||
docker load -i dist/docker/backend.v1.0.0-rc2.tar
|
||||
docker load -i dist/docker/frontend.v1.0.0-rc2.tar
|
||||
docker load -i dist/docker/postgres.v1.0.0-rc2.tar
|
||||
docker load -i dist/docker/backend.v1.0.0-rc2-docker.tar
|
||||
docker load -i dist/docker/frontend.v1.0.0-rc2-docker.tar
|
||||
docker load -i dist/docker/postgres.v1.0.0-rc2-docker.tar
|
||||
|
||||
# 4. Подготовить env из шаблона
|
||||
cp dist/docker/.env.enterprise-clean.example .env.enterprise-clean
|
||||
|
||||
# 4a. Для первого запуска задать bootstrap администратора
|
||||
# INITIAL_ADMIN_CREATE=true
|
||||
# INITIAL_ADMIN_USERNAME=<org-admin-login>
|
||||
# INITIAL_ADMIN_PASSWORD=<temporary-strong-secret>
|
||||
|
||||
# 5. Запустить только локальные образы
|
||||
docker compose --env-file .env.enterprise-clean -f dist/docker/docker-compose.enterprise-clean.yml up -d
|
||||
```
|
||||
|
||||
Bootstrap администратора выполняется entrypoint-скриптом внутри backend container:
|
||||
- если `INITIAL_ADMIN_CREATE=true`, контейнер вызывает [`create_admin.py`](backend/src/scripts/create_admin.py) перед стартом API;
|
||||
- если администратор уже существует, учётная запись не меняется;
|
||||
- теги в [`.env.enterprise-clean.example`](.env.enterprise-clean.example) должны совпадать с фактически загруженными образами `ss-tools-backend:v1.0.0-rc2-docker` и `ss-tools-frontend:v1.0.0-rc2-docker`;
|
||||
- после первого входа пароль должен быть ротирован, а `INITIAL_ADMIN_CREATE` возвращён в `false`.
|
||||
|
||||
Ограничения для production-grade offline release:
|
||||
- build не должен тянуть зависимости в изолированном контуре;
|
||||
- все base images должны быть заранее зеркалированы во внутренний registry или поставляться как tar;
|
||||
|
||||
@@ -31,10 +31,13 @@ from src.core.logger import logger, belief_scope
|
||||
#
|
||||
# @PARAM: username (str) - Admin username.
|
||||
# @PARAM: password (str) - Admin password.
|
||||
def create_admin(username, password):
|
||||
# @PARAM: email (str | None) - Optional admin email.
|
||||
def create_admin(username, password, email=None):
|
||||
with belief_scope("create_admin"):
|
||||
db = AuthSessionLocal()
|
||||
try:
|
||||
normalized_email = email.strip() if isinstance(email, str) and email.strip() else None
|
||||
|
||||
# 1. Ensure Admin role exists
|
||||
admin_role = db.query(Role).filter(Role.name == "Admin").first()
|
||||
if not admin_role:
|
||||
@@ -48,12 +51,13 @@ def create_admin(username, password):
|
||||
existing_user = db.query(User).filter(User.username == username).first()
|
||||
if existing_user:
|
||||
logger.warning(f"User {username} already exists.")
|
||||
return
|
||||
return "exists"
|
||||
|
||||
# 3. Create Admin user
|
||||
logger.info(f"Creating admin user: {username}")
|
||||
new_user = User(
|
||||
username=username,
|
||||
email=normalized_email,
|
||||
password_hash=get_password_hash(password),
|
||||
auth_source="LOCAL",
|
||||
is_active=True
|
||||
@@ -62,10 +66,12 @@ def create_admin(username, password):
|
||||
db.add(new_user)
|
||||
db.commit()
|
||||
logger.info(f"Admin user {username} created successfully.")
|
||||
|
||||
return "created"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create admin user: {e}")
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
# [/DEF:create_admin:Function]
|
||||
@@ -74,10 +80,15 @@ if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Create initial admin user")
|
||||
parser.add_argument("--username", required=True, help="Admin username")
|
||||
parser.add_argument("--password", required=True, help="Admin password")
|
||||
parser.add_argument("--email", required=False, help="Admin email")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Ensure DB is initialized before creating admin
|
||||
init_db()
|
||||
create_admin(args.username, args.password)
|
||||
|
||||
try:
|
||||
# Ensure DB is initialized before creating admin
|
||||
init_db()
|
||||
create_admin(args.username, args.password, args.email)
|
||||
sys.exit(0)
|
||||
except Exception:
|
||||
sys.exit(1)
|
||||
|
||||
# [/DEF:backend.src.scripts.create_admin:Module]
|
||||
@@ -12,6 +12,7 @@ from src.models.auth import User, Role, Permission, ADGroupMapping
|
||||
from src.services.auth_service import AuthService
|
||||
from src.core.auth.repository import AuthRepository
|
||||
from src.core.auth.security import verify_password, get_password_hash
|
||||
from src.scripts.create_admin import create_admin
|
||||
|
||||
# Create in-memory SQLite database for testing
|
||||
SQLALCHEMY_DATABASE_URL = "sqlite:///:memory:"
|
||||
@@ -159,3 +160,32 @@ def test_ad_group_mapping(auth_repo):
|
||||
retrieved_mapping = auth_repo.db.query(ADGroupMapping).filter_by(ad_group="DOMAIN\\ADFS_Admins").first()
|
||||
assert retrieved_mapping is not None
|
||||
assert retrieved_mapping.role_id == role.id
|
||||
|
||||
|
||||
def test_create_admin_creates_user_with_optional_email(monkeypatch, db_session):
|
||||
"""Test bootstrap admin creation stores optional email and Admin role"""
|
||||
monkeypatch.setattr("src.scripts.create_admin.AuthSessionLocal", lambda: db_session)
|
||||
|
||||
result = create_admin("bootstrap-admin", "bootstrap-pass", "admin@example.com")
|
||||
|
||||
created_user = db_session.query(User).filter(User.username == "bootstrap-admin").first()
|
||||
assert result == "created"
|
||||
assert created_user is not None
|
||||
assert created_user.email == "admin@example.com"
|
||||
assert created_user.roles[0].name == "Admin"
|
||||
|
||||
|
||||
def test_create_admin_is_idempotent_for_existing_user(monkeypatch, db_session):
|
||||
"""Test bootstrap admin creation preserves existing user on repeated runs"""
|
||||
monkeypatch.setattr("src.scripts.create_admin.AuthSessionLocal", lambda: db_session)
|
||||
|
||||
first_result = create_admin("bootstrap-admin-2", "bootstrap-pass")
|
||||
second_result = create_admin("bootstrap-admin-2", "new-password", "changed@example.com")
|
||||
|
||||
created_user = db_session.query(User).filter(User.username == "bootstrap-admin-2").first()
|
||||
assert first_result == "created"
|
||||
assert second_result == "exists"
|
||||
assert created_user is not None
|
||||
assert created_user.email is None
|
||||
assert verify_password("bootstrap-pass", created_user.password_hash)
|
||||
assert not verify_password("new-password", created_user.password_hash)
|
||||
|
||||
@@ -33,6 +33,10 @@ services:
|
||||
BACKEND_PORT: 8000
|
||||
ENABLE_BELIEF_STATE_LOGGING: ${ENABLE_BELIEF_STATE_LOGGING:-true}
|
||||
TASK_LOG_LEVEL: ${TASK_LOG_LEVEL:-INFO}
|
||||
INITIAL_ADMIN_CREATE: ${INITIAL_ADMIN_CREATE:-false}
|
||||
INITIAL_ADMIN_USERNAME: ${INITIAL_ADMIN_USERNAME:-admin}
|
||||
INITIAL_ADMIN_PASSWORD: ${INITIAL_ADMIN_PASSWORD:-}
|
||||
INITIAL_ADMIN_EMAIL: ${INITIAL_ADMIN_EMAIL:-}
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
||||
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
||||
ports:
|
||||
|
||||
57
docker/backend.entrypoint.sh
Executable file
57
docker/backend.entrypoint.sh
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# [DEF:docker.backend.entrypoint:Module]
|
||||
# @TIER: STANDARD
|
||||
# @SEMANTICS: docker, entrypoint, admin-bootstrap, runtime, backend
|
||||
# @PURPOSE: Container entrypoint that performs optional idempotent admin bootstrap before starting backend runtime.
|
||||
# @LAYER: Infra
|
||||
# @RELATION: DEPENDS_ON -> backend/src/scripts/create_admin.py
|
||||
# @INVARIANT: Existing admin account must never be overwritten during container restarts.
|
||||
# [/DEF:docker.backend.entrypoint:Module]
|
||||
|
||||
# [DEF:docker.backend.entrypoint.bootstrap_admin:Function]
|
||||
# @PURPOSE: Execute optional initial admin bootstrap from runtime environment variables.
|
||||
# @PRE: Python runtime and backend sources are available inside /app/backend.
|
||||
# @POST: Admin is created only when INITIAL_ADMIN_CREATE=true and required credentials are present.
|
||||
bootstrap_admin() {
|
||||
local create_flag="${INITIAL_ADMIN_CREATE:-false}"
|
||||
local username="${INITIAL_ADMIN_USERNAME:-}"
|
||||
local password="${INITIAL_ADMIN_PASSWORD:-}"
|
||||
local email="${INITIAL_ADMIN_EMAIL:-}"
|
||||
|
||||
case "${create_flag,,}" in
|
||||
true|1|yes|y)
|
||||
;;
|
||||
*)
|
||||
echo "[entrypoint] INITIAL_ADMIN_CREATE is disabled; skipping admin bootstrap"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -z "${username}" ]]; then
|
||||
echo "[entrypoint] INITIAL_ADMIN_USERNAME is required when INITIAL_ADMIN_CREATE=true" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -z "${password}" ]]; then
|
||||
echo "[entrypoint] INITIAL_ADMIN_PASSWORD is required when INITIAL_ADMIN_CREATE=true" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "[entrypoint] initializing auth database"
|
||||
python3 src/scripts/init_auth_db.py
|
||||
|
||||
echo "[entrypoint] running idempotent admin bootstrap for user '${username}'"
|
||||
if [[ -n "${email}" ]]; then
|
||||
python3 src/scripts/create_admin.py --username "${username}" --password "${password}" --email "${email}"
|
||||
else
|
||||
python3 src/scripts/create_admin.py --username "${username}" --password "${password}"
|
||||
fi
|
||||
}
|
||||
# [/DEF:docker.backend.entrypoint.bootstrap_admin:Function]
|
||||
|
||||
bootstrap_admin
|
||||
|
||||
echo "[entrypoint] starting backend: $*"
|
||||
exec "$@"
|
||||
@@ -152,15 +152,15 @@ psql -U postgres -d ss_tools
|
||||
|
||||
```bash
|
||||
cd /home/busya/dev/ss-tools
|
||||
./scripts/build_offline_docker_bundle.sh v1.0.0-rc2
|
||||
./scripts/build_offline_docker_bundle.sh v1.0.0-rc2-docker
|
||||
```
|
||||
|
||||
Результат появится в `dist/docker/`:
|
||||
- `backend.v1.0.0-rc2.tar`
|
||||
- `frontend.v1.0.0-rc2.tar`
|
||||
- `postgres.v1.0.0-rc2.tar`
|
||||
- `sha256sums.v1.0.0-rc2.txt`
|
||||
- `manifest.v1.0.0-rc2.txt`
|
||||
- `backend.v1.0.0-rc2-docker.tar`
|
||||
- `frontend.v1.0.0-rc2-docker.tar`
|
||||
- `postgres.v1.0.0-rc2-docker.tar`
|
||||
- `sha256sums.v1.0.0-rc2-docker.txt`
|
||||
- `manifest.v1.0.0-rc2-docker.txt`
|
||||
- `docker-compose.enterprise-clean.yml`
|
||||
- `.env.enterprise-clean.example`
|
||||
|
||||
@@ -171,9 +171,9 @@ cd /home/busya/dev/ss-tools
|
||||
### 3. Импорт образов
|
||||
|
||||
```bash
|
||||
docker load -i backend.v1.0.0-rc2.tar
|
||||
docker load -i frontend.v1.0.0-rc2.tar
|
||||
docker load -i postgres.v1.0.0-rc2.tar
|
||||
docker load -i backend.v1.0.0-rc2-docker.tar
|
||||
docker load -i frontend.v1.0.0-rc2-docker.tar
|
||||
docker load -i postgres.v1.0.0-rc2-docker.tar
|
||||
```
|
||||
|
||||
### 4. Подготовка конфигурации
|
||||
@@ -189,6 +189,16 @@ cp .env.enterprise-clean.example .env.enterprise-clean
|
||||
- `POSTGRES_PASSWORD`
|
||||
- `STORAGE_ROOT`
|
||||
|
||||
Для первого запуска в новом контуре дополнительно задайте:
|
||||
- `INITIAL_ADMIN_CREATE=true`
|
||||
- `INITIAL_ADMIN_USERNAME=<org-admin-login>`
|
||||
- `INITIAL_ADMIN_PASSWORD=<temporary-strong-secret>`
|
||||
- `INITIAL_ADMIN_EMAIL=<optional>`
|
||||
|
||||
Также проверьте, что теги образов в [`.env.enterprise-clean`](.env.enterprise-clean.example) совпадают с реально загруженными:
|
||||
- `BACKEND_IMAGE=ss-tools-backend:v1.0.0-rc2-docker`
|
||||
- `FRONTEND_IMAGE=ss-tools-frontend:v1.0.0-rc2-docker`
|
||||
|
||||
### 5. Запуск в offline-контуре
|
||||
|
||||
```bash
|
||||
@@ -197,6 +207,21 @@ docker compose --env-file .env.enterprise-clean -f docker-compose.enterprise-cle
|
||||
|
||||
Compose-файл использует `pull_policy: never`, поэтому runtime не должен обращаться к внешним registry.
|
||||
|
||||
### 6. Bootstrap администратора в контейнере
|
||||
|
||||
При `INITIAL_ADMIN_CREATE=true` backend container автоматически:
|
||||
1. инициализирует auth DB;
|
||||
2. запускает [`create_admin.py`](../backend/src/scripts/create_admin.py) с runtime-параметрами;
|
||||
3. создаёт пользователя только если его ещё нет;
|
||||
4. при повторном старте не изменяет существующего администратора.
|
||||
|
||||
После первого успешного входа обязательно:
|
||||
- смените bootstrap-пароль на постоянный организационный секрет;
|
||||
- установите `INITIAL_ADMIN_CREATE=false`;
|
||||
- перезапустите stack с обновлённым `.env.enterprise-clean`.
|
||||
|
||||
Если bootstrap завершается ошибкой, backend не стартует — это ожидаемый fail-fast режим для безопасного ввода в эксплуатацию.
|
||||
|
||||
## Первая настройка
|
||||
|
||||
### 1. Инициализация базы данных
|
||||
@@ -450,6 +475,24 @@ export CLEAN_TUI_ARTIFACTS_JSON=/absolute/path/artifacts.json
|
||||
}
|
||||
```
|
||||
|
||||
Минимальный пример `artifacts.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "artifact-backend-dist",
|
||||
"path": "backend/dist/package.tar.gz",
|
||||
"sha256": "deadbeef",
|
||||
"size": 1024,
|
||||
"category": "core",
|
||||
"source_uri": "https://repo.intra.company.local/releases/backend/dist/package.tar.gz",
|
||||
"source_host": "repo.intra.company.local"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Политика источников (internal-only)
|
||||
|
||||
Разрешены только хосты из внутреннего реестра компании, например:
|
||||
|
||||
@@ -73,6 +73,7 @@ postgres_image_id=${POSTGRES_IMAGE_ID}
|
||||
postgres_repo_digest=${POSTGRES_REPO_DIGEST}
|
||||
compose_file=docker-compose.enterprise-clean.yml
|
||||
env_template=.env.enterprise-clean.example
|
||||
env_bootstrap_fields=INITIAL_ADMIN_CREATE,INITIAL_ADMIN_USERNAME,INITIAL_ADMIN_PASSWORD,INITIAL_ADMIN_EMAIL
|
||||
checksums_file=sha256sums.${TAG}.txt
|
||||
generated_at_utc=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
EOF
|
||||
@@ -105,6 +106,12 @@ cat > "${DIST_ROOT}/manifest.${TAG}.json" <<EOF
|
||||
],
|
||||
"compose_file": "docker-compose.enterprise-clean.yml",
|
||||
"env_template": ".env.enterprise-clean.example",
|
||||
"env_bootstrap_fields": [
|
||||
"INITIAL_ADMIN_CREATE",
|
||||
"INITIAL_ADMIN_USERNAME",
|
||||
"INITIAL_ADMIN_PASSWORD",
|
||||
"INITIAL_ADMIN_EMAIL"
|
||||
],
|
||||
"checksums_file": "sha256sums.${TAG}.txt"
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -34,7 +34,8 @@
|
||||
- 1 enterprise release flow;
|
||||
- 1 TUI сценарий подготовки/проверки;
|
||||
- 6–9 новых/обновлённых модулей (+config_loader, filesystem_scanner, db_cleanup_executor);
|
||||
- документация и контракты в пределах feature-папки.
|
||||
- документация и контракты в пределах feature-папки;
|
||||
- runtime bootstrap администратора через переменные `.env.enterprise-clean` без hardcode секретов в образах.
|
||||
|
||||
## Constitution Check
|
||||
|
||||
@@ -167,7 +168,8 @@ frontend/
|
||||
|
||||
## Implementation Traceability & Final Notes
|
||||
|
||||
- Статус реализации: Phase 1–7 завершены (T001–T043).
|
||||
- Статус реализации: Phase 1–7 завершены (T001–T044).
|
||||
- Новое расширение (post-release hardening, 2026-03-13): добавлен scope на управляемый bootstrap администратора через `.env.enterprise-clean` и docker startup flow.
|
||||
- Ключевые подтверждения polish-фазы:
|
||||
- T039: smoke TUI сценария зафиксирован в [`quickstart.md`](./quickstart.md).
|
||||
- T040: контрактная проверка API подтверждена тестом [`backend/tests/api/routes/test_clean_release_api.py`](../../backend/tests/api/routes/test_clean_release_api.py).
|
||||
@@ -175,7 +177,30 @@ frontend/
|
||||
- T042: governance conflict по префиксу закрыт и задокументирован.
|
||||
- T043: добавлена итоговая traceability-нотация в текущем плане.
|
||||
|
||||
Итог: feature готова к финальному релизному циклу с обязательным CI gate (`COMPLIANT` only) и операционной доказательной базой для аудита.
|
||||
Итог: базовая feature готова к финальному релизному циклу с обязательным CI gate (`COMPLIANT` only) и операционной доказательной базой для аудита.
|
||||
|
||||
## Post-Release Hardening Addendum — Admin Bootstrap via `.env.enterprise-clean`
|
||||
|
||||
Цель addendum: убрать ручной шаг создания initial admin после доставки offline bundle и сделать это управляемой частью container startup.
|
||||
|
||||
Архитектурные решения:
|
||||
1. В `.env.enterprise-clean.example` добавить параметры:
|
||||
- `INITIAL_ADMIN_CREATE=false` (default-safe),
|
||||
- `INITIAL_ADMIN_USERNAME=admin`,
|
||||
- `INITIAL_ADMIN_PASSWORD=change-me`,
|
||||
- `INITIAL_ADMIN_EMAIL=` (optional).
|
||||
2. В backend image добавить entrypoint-скрипт:
|
||||
- запускает проверку флага `INITIAL_ADMIN_CREATE`,
|
||||
- при `true` вызывает существующий скрипт создания администратора,
|
||||
- обрабатывает idempotency (существующий пользователь => без ошибки, лог `already exists`).
|
||||
3. В `docker-compose.enterprise-clean.yml` прокинуть переменные bootstrap администратора только в `backend` service.
|
||||
4. В операционном runbook зафиксировать обязательную ротацию bootstrap-пароля после первого входа.
|
||||
5. В offline bundle manifest оставить ссылку на `.env.enterprise-clean.example` как source-of-truth для параметров запуска.
|
||||
|
||||
Нефункциональные ограничения:
|
||||
- Никаких default production секретов в Git.
|
||||
- Повторный restart контейнера не должен менять существующего admin.
|
||||
- Ошибка bootstrap не должна маскироваться: должна логироваться и приводить к fail-fast старта backend (чтобы оператор устранил причину до ввода в эксплуатацию).
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
|
||||
@@ -73,6 +73,23 @@ cd /home/busya/dev/ss-tools
|
||||
2. Повторно запустить проверку из того же TUI экрана (`F5`).
|
||||
3. Повторять до статуса `COMPLIANT`.
|
||||
|
||||
## Admin Bootstrap via `.env.enterprise-clean` (Docker Runtime)
|
||||
|
||||
Для offline bundle deployment в контейнерах используйте runtime файл `.env.enterprise-clean` на базе шаблона `.env.enterprise-clean.example`.
|
||||
|
||||
Обязательные параметры bootstrap:
|
||||
- `INITIAL_ADMIN_CREATE=true` для первого запуска;
|
||||
- `INITIAL_ADMIN_USERNAME=<org-admin-login>`;
|
||||
- `INITIAL_ADMIN_PASSWORD=<strong-temporary-secret>`;
|
||||
- `INITIAL_ADMIN_EMAIL=<optional>`.
|
||||
|
||||
Ожидаемое поведение:
|
||||
1. Backend container стартует.
|
||||
2. EntryPoint проверяет флаг `INITIAL_ADMIN_CREATE`.
|
||||
3. Если пользователь не существует — создаётся Admin user.
|
||||
4. Если пользователь уже существует — bootstrap шага пропускается без изменения учётной записи.
|
||||
5. После первого входа оператор выполняет обязательную ротацию bootstrap-пароля.
|
||||
|
||||
## CI Gate (обязательный)
|
||||
|
||||
После операторского прогона TUI та же политика должна быть проверена в CI.
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
- Что происходит, если внутренний сервер ресурсов временно недоступен во время развёртывания в изолированном контуре?
|
||||
- Как система реагирует, если в конфигурации присутствует косвенная ссылка на внешний интернет-источник (например, зеркальный URL вне корпоративного домена)?
|
||||
- Что происходит при повторной подготовке clean-дистрибутива для уже очищенного релиз-кандидата?
|
||||
- Что происходит, если контейнер перезапускается с `INITIAL_ADMIN_CREATE=true` и администратор уже существует?
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
@@ -101,6 +102,9 @@
|
||||
- **FR-018**: Стадия `NO_EXTERNAL_ENDPOINTS` MUST сканировать все текстовые файлы (включая код, конфиги, скрипты) на наличие URL/хостов и сверять каждый найденный endpoint с `allowed_sources`.
|
||||
- **FR-019**: Процесс clean-подготовки MUST включать стадию очистки БД от тестовых пользователей и демо-данных. Правила очистки (таблицы, условия, исключения) задаются в секции `database_cleanup` файла `.clean-release.yaml`.
|
||||
- **FR-020**: Структура `.clean-release.yaml` MUST включать секции: `profile`, `scan_mode`, `prohibited_categories`, `prohibited_paths`, `allowed_sources`, `ignore_paths`, `database_cleanup` (с подсекциями `tables` и `preserve`).
|
||||
- **FR-021**: Enterprise clean release bundle MUST включать `.env.enterprise-clean.example` с параметрами bootstrap администратора (`INITIAL_ADMIN_USERNAME`, `INITIAL_ADMIN_PASSWORD`, `INITIAL_ADMIN_EMAIL` optional, `INITIAL_ADMIN_CREATE`) и безопасными комментариями по ротации пароля.
|
||||
- **FR-022**: Docker startup flow backend MUST поддерживать idempotent bootstrap администратора при запуске контейнера: при `INITIAL_ADMIN_CREATE=true` система создаёт администратора только если пользователь отсутствует и не перезаписывает существующие credentials/roles.
|
||||
- **FR-023**: Процесс публикации clean bundle MUST включать операторский сценарий заполнения runtime `.env.enterprise-clean` из шаблона и подтверждение, что bootstrap-пароль заменён на организационный секрет до первого production запуска.
|
||||
|
||||
### Key Entities *(include if feature involves data)*
|
||||
|
||||
|
||||
@@ -141,6 +141,20 @@
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Post-Release Hardening — Admin Bootstrap in Docker
|
||||
|
||||
**Purpose**: Автоматизировать первичное создание администратора через runtime `.env.enterprise-clean` в offline/enterprise deployment.
|
||||
|
||||
- [X] T045 Add admin bootstrap env contract to `.env.enterprise-clean.example` (`INITIAL_ADMIN_CREATE`, `INITIAL_ADMIN_USERNAME`, `INITIAL_ADMIN_PASSWORD`, optional `INITIAL_ADMIN_EMAIL`)
|
||||
- [X] T046 Wire admin bootstrap envs to backend runtime in `docker-compose.enterprise-clean.yml`
|
||||
- [X] T047 Add backend entrypoint flow that performs idempotent admin bootstrap before app start in `docker/backend.Dockerfile` and new entrypoint script
|
||||
- [X] T048 Extend admin creation script for optional email and deterministic exit behavior for existing user in `backend/src/scripts/create_admin.py`
|
||||
- [X] T049 Update offline bundle packaging metadata to preserve new env contract in `scripts/build_offline_docker_bundle.sh` and bundle docs
|
||||
- [X] T050 Add deployment runbook section for secure admin bootstrap and mandatory password rotation in `README.md` and `docs/installation.md`
|
||||
- [X] T051 Add regression tests for container bootstrap path and create-admin idempotency in `backend/tests/scripts/` and/or service tests
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Execution Order
|
||||
|
||||
### Phase Dependencies
|
||||
|
||||
Reference in New Issue
Block a user