Merge pull request #41 from ochenstarik-ui/integration/release-signing-consistency

fix(release): publish signing certificate and verify releases the way an operator does
This commit is contained in:
ochenstarik-ui 2026-08-15 13:23:12 +07:00 committed by GitHub
commit b0ad40c5cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 257 additions and 128 deletions

View file

@ -301,7 +301,10 @@ jobs:
- name: Sign Manifest - name: Sign Manifest
shell: bash shell: bash
run: | run: |
cosign sign-blob --yes --output-signature server-monitor-manager-manifest.sig server-monitor-manager-manifest.json cosign sign-blob --yes \
--output-signature server-monitor-manager-manifest.sig \
--output-certificate server-monitor-manager-manifest.pem \
server-monitor-manager-manifest.json
- name: Attach artifacts to GitHub Release - name: Attach artifacts to GitHub Release
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
@ -316,3 +319,4 @@ jobs:
artifacts/server-monitor-manager-win-x64/artifacts/windows-installer/* artifacts/server-monitor-manager-win-x64/artifacts/windows-installer/*
server-monitor-manager-manifest.json server-monitor-manager-manifest.json
server-monitor-manager-manifest.sig server-monitor-manager-manifest.sig
server-monitor-manager-manifest.pem

View file

@ -98,7 +98,7 @@ In the application, generate or copy the monitoring SSH key, add the Hub profile
## Current status ## Current status
`v0.1.0-alpha.13` is an early testing release, not a production security appliance. Windows and Linux builds, control-plane tests, a test-signed x64 MSIX, self-contained `linux-x64`/`linux-arm64` artifacts, and SHA-256 checksums are automated in GitHub Actions. `v0.1.0-alpha.14` is an early testing release, not a production security appliance. Windows and Linux builds, control-plane tests, a test-signed x64 MSIX, self-contained `linux-x64`/`linux-arm64` artifacts, and SHA-256 checksums are automated in GitHub Actions.
The current development branch implements dedicated Windows pages for Servers, Links, Sessions, and Settings; SSH monitoring; directional Links; one-time enrollment; separate mTLS Agent, Operator, and source-scoped Automation identities; certificate revocation/re-enrollment; SQLite control state; audit; authenticated event streaming; Windows Control API integration; and a bounded durable Agent buffer with downsampling. The current development branch implements dedicated Windows pages for Servers, Links, Sessions, and Settings; SSH monitoring; directional Links; one-time enrollment; separate mTLS Agent, Operator, and source-scoped Automation identities; certificate revocation/re-enrollment; SQLite control state; audit; authenticated event streaming; Windows Control API integration; and a bounded durable Agent buffer with downsampling.

View file

@ -73,7 +73,7 @@ Usage:
ochenstarik-server-monitor-manager.sh install-control ARCHIVE PUBLIC_HOST [HTTPS_PORT] ochenstarik-server-monitor-manager.sh install-control ARCHIVE PUBLIC_HOST [HTTPS_PORT]
ochenstarik-server-monitor-manager.sh install-agent ARCHIVE NODE_ID CONTROL_URL CA_CERT ochenstarik-server-monitor-manager.sh install-agent ARCHIVE NODE_ID CONTROL_URL CA_CERT
ochenstarik-server-monitor-manager.sh install-node ARCHIVE ochenstarik-server-monitor-manager.sh install-node ARCHIVE
ochenstarik-server-monitor-manager.sh verify-manifest MANIFEST SIGNATURE ochenstarik-server-monitor-manager.sh verify-manifest MANIFEST SIGNATURE CERTIFICATE
ochenstarik-server-monitor-manager.sh mesh-init PUBLIC_ENDPOINT [WG_PORT] ochenstarik-server-monitor-manager.sh mesh-init PUBLIC_ENDPOINT [WG_PORT]
ochenstarik-server-monitor-manager.sh peer-add SMMPEER1_CODE ochenstarik-server-monitor-manager.sh peer-add SMMPEER1_CODE
ochenstarik-server-monitor-manager.sh mesh-status ochenstarik-server-monitor-manager.sh mesh-status
@ -290,7 +290,7 @@ validate_control_url() {
} }
verify_manifest() { verify_manifest() {
local manifest="$1" signature="$2" local manifest="$1" signature="$2" certificate="$3"
if [[ "${SMM_ALLOW_UNSIGNED:-0}" == "1" ]]; then if [[ "${SMM_ALLOW_UNSIGNED:-0}" == "1" ]]; then
log "WARNING: Signature verification skipped due to SMM_ALLOW_UNSIGNED=1." log "WARNING: Signature verification skipped due to SMM_ALLOW_UNSIGNED=1."
return 0 return 0
@ -299,10 +299,13 @@ verify_manifest() {
[[ -f "$manifest" ]] || fail "Manifest not found: $manifest" [[ -f "$manifest" ]] || fail "Manifest not found: $manifest"
[[ -f "$signature" ]] || fail "Signature not found: $signature" [[ -f "$signature" ]] || fail "Signature not found: $signature"
log "Verifying manifest signature..." log "Verifying manifest signature..."
local verify_args=(--certificate-oidc-issuer "$COSIGN_ISSUER" --certificate-identity-regexp "$COSIGN_IDENTITY_REGEXP") local verify_args
if [[ -n "${SMM_TEST_PUBKEY:-}" ]]; then if [[ -n "${SMM_TEST_PUBKEY:-}" ]]; then
verify_args=(--key "$SMM_TEST_PUBKEY" --insecure-ignore-tlog) verify_args=(--key "$SMM_TEST_PUBKEY" --insecure-ignore-tlog)
log "WARNING: Using test public key for verification. This must NOT happen in production." log "WARNING: Using test public key for verification. This must NOT happen in production."
else
[[ -f "$certificate" ]] || fail "Certificate not found: $certificate"
verify_args=(--certificate "$certificate" --certificate-oidc-issuer "$COSIGN_ISSUER" --certificate-identity-regexp "$COSIGN_IDENTITY_REGEXP")
fi fi
if ! cosign verify-blob "${verify_args[@]}" \ if ! cosign verify-blob "${verify_args[@]}" \
--signature "$signature" "$manifest" >/dev/null 2>&1; then --signature "$signature" "$manifest" >/dev/null 2>&1; then
@ -312,13 +315,14 @@ verify_manifest() {
} }
verify_archive() { verify_archive() {
local archive="$1" expected actual entry manifest signature local archive="$1" expected actual entry manifest signature certificate
[[ -f "$archive" ]] || fail "Archive not found: $archive" [[ -f "$archive" ]] || fail "Archive not found: $archive"
manifest="$(dirname "$archive")/server-monitor-manager-manifest.json" manifest="$(dirname "$archive")/server-monitor-manager-manifest.json"
signature="$(dirname "$archive")/server-monitor-manager-manifest.sig" signature="$(dirname "$archive")/server-monitor-manager-manifest.sig"
certificate="$(dirname "$archive")/server-monitor-manager-manifest.pem"
if [[ -f "$manifest" && -f "$signature" ]]; then if [[ -f "$manifest" && -f "$signature" && -f "$certificate" ]]; then
verify_manifest "$manifest" "$signature" verify_manifest "$manifest" "$signature" "$certificate"
local archive_basename local archive_basename
archive_basename="$(basename "$archive")" archive_basename="$(basename "$archive")"
expected="$(awk -F'"' -v name="$archive_basename" '$2 == name {print $4}' "$manifest" || true)" expected="$(awk -F'"' -v name="$archive_basename" '$2 == name {print $4}' "$manifest" || true)"
@ -328,13 +332,13 @@ verify_archive() {
[[ -n "$expected" ]] || fail "Could not extract archive hash from manifest." [[ -n "$expected" ]] || fail "Could not extract archive hash from manifest."
else else
if [[ "${SMM_ALLOW_UNSIGNED:-0}" == "1" ]]; then if [[ "${SMM_ALLOW_UNSIGNED:-0}" == "1" ]]; then
log "WARNING: Manifest and signature not found, falling back to .sha256 file due to SMM_ALLOW_UNSIGNED=1." log "WARNING: Manifest, signature, or certificate not found; falling back to .sha256 file due to SMM_ALLOW_UNSIGNED=1."
local checksum_file="${archive}.sha256" local checksum_file="${archive}.sha256"
[[ -f "$checksum_file" ]] || fail "Checksum file not found: $checksum_file" [[ -f "$checksum_file" ]] || fail "Checksum file not found: $checksum_file"
expected="$(awk 'NR == 1 { print $1 }' "$checksum_file")" expected="$(awk 'NR == 1 { print $1 }' "$checksum_file")"
[[ "$expected" =~ ^[0-9a-fA-F]{64}$ ]] || fail "Invalid checksum file: $checksum_file" [[ "$expected" =~ ^[0-9a-fA-F]{64}$ ]] || fail "Invalid checksum file: $checksum_file"
else else
fail "Manifest and signature are required for archive verification. Set SMM_ALLOW_UNSIGNED=1 to bypass." fail "Manifest, signature, and certificate are required for archive verification. Set SMM_ALLOW_UNSIGNED=1 to bypass."
fi fi
fi fi
@ -1493,7 +1497,7 @@ main() {
install-control) [[ $# -ge 2 && $# -le 3 ]] || fail "install-control requires ARCHIVE PUBLIC_HOST [HTTPS_PORT]"; install_control "$@" ;; install-control) [[ $# -ge 2 && $# -le 3 ]] || fail "install-control requires ARCHIVE PUBLIC_HOST [HTTPS_PORT]"; install_control "$@" ;;
install-agent) [[ $# -eq 4 ]] || fail "install-agent requires ARCHIVE NODE_ID CONTROL_URL CA_CERT"; install_agent "$@" ;; install-agent) [[ $# -eq 4 ]] || fail "install-agent requires ARCHIVE NODE_ID CONTROL_URL CA_CERT"; install_agent "$@" ;;
install-node) [[ $# -eq 1 ]] || fail "install-node requires ARCHIVE"; install_node_from_code "$1" ;; install-node) [[ $# -eq 1 ]] || fail "install-node requires ARCHIVE"; install_node_from_code "$1" ;;
verify-manifest) [[ $# -eq 2 ]] || fail "verify-manifest requires MANIFEST SIGNATURE"; verify_manifest "$1" "$2" ;; verify-manifest) [[ $# -eq 3 ]] || fail "verify-manifest requires MANIFEST SIGNATURE CERTIFICATE"; verify_manifest "$1" "$2" "$3" ;;
install-monitor) [[ $# -eq 1 ]] || fail "install-monitor requires PUBLIC_KEY"; install_monitor "$1" ;; install-monitor) [[ $# -eq 1 ]] || fail "install-monitor requires PUBLIC_KEY"; install_monitor "$1" ;;
uninstall-monitor) [[ $# -eq 0 ]] || fail "uninstall-monitor takes no arguments"; uninstall_monitor ;; uninstall-monitor) [[ $# -eq 0 ]] || fail "uninstall-monitor takes no arguments"; uninstall_monitor ;;
mesh-init) [[ $# -ge 1 && $# -le 2 ]] || fail "mesh-init requires PUBLIC_ENDPOINT [WG_PORT]"; mesh_init "$@" ;; mesh-init) [[ $# -ge 1 && $# -le 2 ]] || fail "mesh-init requires PUBLIC_ENDPOINT [WG_PORT]"; mesh_init "$@" ;;

View file

@ -3,7 +3,7 @@ set -Eeuo pipefail
IFS=$'\n\t' IFS=$'\n\t'
readonly PROGRAM_NAME="smm-setup" readonly PROGRAM_NAME="smm-setup"
readonly DEFAULT_RELEASE_TAG="v0.1.0-alpha.13" readonly DEFAULT_RELEASE_TAG="v0.1.0-alpha.14"
readonly DEFAULT_REPOSITORY="ochenstarik-ui/server-monitor-manager" readonly DEFAULT_REPOSITORY="ochenstarik-ui/server-monitor-manager"
readonly INNER_ASSET="ochenstarik-server-monitor-manager.sh" readonly INNER_ASSET="ochenstarik-server-monitor-manager.sh"
@ -22,7 +22,7 @@ asset from the selected immutable GitHub release. Common commands:
backup-create | backup-restore | version backup-create | backup-restore | version
Environment overrides: Environment overrides:
SMM_TAG Release tag (default: v0.1.0-alpha.13) SMM_TAG Release tag (default: v0.1.0-alpha.14)
SMM_REPOSITORY GitHub repository (default: ochenstarik-ui/server-monitor-manager) SMM_REPOSITORY GitHub repository (default: ochenstarik-ui/server-monitor-manager)
SMM_CACHE_DIR Verified-download cache directory SMM_CACHE_DIR Verified-download cache directory
USAGE USAGE

View file

@ -32,7 +32,7 @@ sudo ./ochenstarik-server-monitor-manager.sh hub
لا توجد كلمة مرور root مشتركة ولا يغادر مفتاح WireGuard الخاص عقدته. هويات monitoring وterminal وAgent وOperator وأتمتة AI منفصلة. يستخدم SSH أمراً إجبارياً بلا shell أو PTY أو forwarding؛ يقيّد mTLS الصلاحيات؛ تسمح nftables بالروابط الصريحة فقط؛ ويحفظ SQLite الحالة المطلوبة والتدقيق قبل تعديل الجدار الناري. لا توجد كلمة مرور root مشتركة ولا يغادر مفتاح WireGuard الخاص عقدته. هويات monitoring وterminal وAgent وOperator وأتمتة AI منفصلة. يستخدم SSH أمراً إجبارياً بلا shell أو PTY أو forwarding؛ يقيّد mTLS الصلاحيات؛ تسمح nftables بالروابط الصريحة فقط؛ ويحفظ SQLite الحالة المطلوبة والتدقيق قبل تعديل الجدار الناري.
الإصدار `v0.1.0-alpha.13` للاختبار. يتضمن فرع التطوير الحالي عميل Windows ومثبت Hub/Node وLinks وmTLS وإلغاء الشهادات وإعادة التسجيل وSQLite والتدقيق والأحداث ومخزناً محدوداً دون اتصال مع downsampling. المتبقي: مصالحة إعادة الاتصال، اختبار 50100 Node ومثبت Windows موقّع. الإصدار `v0.1.0-alpha.14` للاختبار. يتضمن فرع التطوير الحالي عميل Windows ومثبت Hub/Node وLinks وmTLS وإلغاء الشهادات وإعادة التسجيل وSQLite والتدقيق والأحداث ومخزناً محدوداً دون اتصال مع downsampling. المتبقي: مصالحة إعادة الاتصال، اختبار 50100 Node ومثبت Windows موقّع.
## الترخيص ## الترخيص

View file

@ -32,7 +32,7 @@ sudo ./ochenstarik-server-monitor-manager.sh hub
Es gibt kein gemeinsames Root-Passwort; der private WireGuard-Schlüssel bleibt auf dem Node. Monitoring-, Terminal-, Agent-, Operator- und KI-Automationsidentitäten sind getrennt. SSH nutzt einen forced-command ohne Shell/PTY/Forwarding; mTLS begrenzt Rollen; nftables erlaubt nur explizite Links; SQLite speichert Sollzustand und Audit vor der Firewalländerung. Es gibt kein gemeinsames Root-Passwort; der private WireGuard-Schlüssel bleibt auf dem Node. Monitoring-, Terminal-, Agent-, Operator- und KI-Automationsidentitäten sind getrennt. SSH nutzt einen forced-command ohne Shell/PTY/Forwarding; mTLS begrenzt Rollen; nftables erlaubt nur explizite Links; SQLite speichert Sollzustand und Audit vor der Firewalländerung.
`v0.1.0-alpha.13` ist eine Testversion. Der aktuelle Entwicklungszweig enthält Windows-Client, Hub/Node-Installer, Links, mTLS, Zertifikatswiderruf und erneute Registrierung, SQLite, Audit, Events, einen begrenzten Offline-Puffer mit Downsampling und dauerhaften Reconnect-Abgleich. Offen sind der Lasttest mit 50100 Nodes und ein signierter Windows-Installer. `v0.1.0-alpha.14` ist eine Testversion. Der aktuelle Entwicklungszweig enthält Windows-Client, Hub/Node-Installer, Links, mTLS, Zertifikatswiderruf und erneute Registrierung, SQLite, Audit, Events, einen begrenzten Offline-Puffer mit Downsampling und dauerhaften Reconnect-Abgleich. Offen sind der Lasttest mit 50100 Nodes und ein signierter Windows-Installer.
## Lizenz ## Lizenz

View file

@ -34,7 +34,7 @@ Abra UDP `51820` y TCP `7443` en el Hub, cree códigos para los Nodes e instále
No hay contraseña root compartida ni claves WireGuard privadas de Nodes en el Hub. Las identidades de monitorización, terminal, Agent, Operator y automatización IA están separadas. SSH usa un forced-command sin shell, PTY ni forwarding; mTLS limita cada rol; nftables permite únicamente Links explícitos; SQLite conserva estado y auditoría antes de aplicar cambios. No hay contraseña root compartida ni claves WireGuard privadas de Nodes en el Hub. Las identidades de monitorización, terminal, Agent, Operator y automatización IA están separadas. SSH usa un forced-command sin shell, PTY ni forwarding; mTLS limita cada rol; nftables permite únicamente Links explícitos; SQLite conserva estado y auditoría antes de aplicar cambios.
`v0.1.0-alpha.13` es una versión de prueba. La rama actual incluye cliente Windows, instalador Hub/Node, Links, mTLS, revocación y reinscripción de certificados, SQLite, auditoría, eventos y un búfer offline limitado con downsampling. Faltan la reconciliación tras reconexión, prueba de 50100 Nodes e instalador Windows firmado. `v0.1.0-alpha.14` es una versión de prueba. La rama actual incluye cliente Windows, instalador Hub/Node, Links, mTLS, revocación y reinscripción de certificados, SQLite, auditoría, eventos y un búfer offline limitado con downsampling. Faltan la reconciliación tras reconexión, prueba de 50100 Nodes e instalador Windows firmado.
## Licencia ## Licencia

View file

@ -32,7 +32,7 @@ Ouvrez UDP `51820` et TCP `7443` sur le Hub, créez les codes et installez les a
Aucun mot de passe root n'est partagé et la clé WireGuard privée reste sur le Node. Les identités monitoring, terminal, Agent, Operator et automatisation IA sont séparées. SSH emploie une forced-command sans shell/PTY/forwarding ; mTLS limite les rôles ; nftables n'autorise que les Links explicites ; SQLite enregistre état et audit avant le changement de pare-feu. Aucun mot de passe root n'est partagé et la clé WireGuard privée reste sur le Node. Les identités monitoring, terminal, Agent, Operator et automatisation IA sont séparées. SSH emploie une forced-command sans shell/PTY/forwarding ; mTLS limite les rôles ; nftables n'autorise que les Links explicites ; SQLite enregistre état et audit avant le changement de pare-feu.
`v0.1.0-alpha.13` est destiné aux tests. La branche actuelle contient le client Windows, l'installateur Hub/Node, les Links, mTLS, la révocation et le ré-enrôlement des certificats, SQLite, l'audit, les événements et un tampon hors ligne limité avec downsampling. Restent la réconciliation, le test de 50100 Nodes et l'installateur Windows signé. `v0.1.0-alpha.14` est destiné aux tests. La branche actuelle contient le client Windows, l'installateur Hub/Node, les Links, mTLS, la révocation et le ré-enrôlement des certificats, SQLite, l'audit, les événements et un tampon hors ligne limité avec downsampling. Restent la réconciliation, le test de 50100 Nodes et l'installateur Windows signé.
## Licence ## Licence

View file

@ -32,7 +32,7 @@ Hub पर UDP `51820` और TCP `7443` खोलें, Node codes बना
Shared root password नहीं है और Node की WireGuard private key Node से बाहर नहीं जाती। Monitoring, terminal, Agent, Operator और AI automation identities अलग हैं। SSH forced-command shell/PTY/forwarding नहीं देता; mTLS roles सीमित करता है; nftables केवल explicit Links स्वीकारता है; SQLite पहले desired state और audit सहेजता है। Shared root password नहीं है और Node की WireGuard private key Node से बाहर नहीं जाती। Monitoring, terminal, Agent, Operator और AI automation identities अलग हैं। SSH forced-command shell/PTY/forwarding नहीं देता; mTLS roles सीमित करता है; nftables केवल explicit Links स्वीकारता है; SQLite पहले desired state और audit सहेजता है।
`v0.1.0-alpha.13` testing release है। Current development branch में Windows client, Hub/Node installer, Links, mTLS, certificate revocation और re-enrollment, SQLite, audit, event stream, downsampling वाला सीमित offline buffer और durable reconnect reconciliation तैयार हैं। 50100 Node load test और signed Windows installer अभी बाकी हैं। `v0.1.0-alpha.14` testing release है। Current development branch में Windows client, Hub/Node installer, Links, mTLS, certificate revocation और re-enrollment, SQLite, audit, event stream, downsampling वाला सीमित offline buffer और durable reconnect reconciliation तैयार हैं। 50100 Node load test और signed Windows installer अभी बाकी हैं।
## लाइसेंस ## लाइसेंस

View file

@ -32,7 +32,7 @@ Hub で UDP `51820` と TCP `7443` を開き、Node コードを作成して他
共有 root パスワードはなく、Node の WireGuard 秘密鍵は Node 外に出ません。monitoring、terminal、Agent、Operator、AI automation の ID は分離されています。SSH は shell/PTY/forwarding のない forced-command、mTLS はロール制限、nftables は明示 Link のみを許可し、SQLite は firewall 変更前に状態と監査を保存します。 共有 root パスワードはなく、Node の WireGuard 秘密鍵は Node 外に出ません。monitoring、terminal、Agent、Operator、AI automation の ID は分離されています。SSH は shell/PTY/forwarding のない forced-command、mTLS はロール制限、nftables は明示 Link のみを許可し、SQLite は firewall 変更前に状態と監査を保存します。
`v0.1.0-alpha.13` はテスト版です。現在の開発ブランチには Windows client、Hub/Node installer、Links、mTLS、証明書失効と再登録、SQLite、監査、イベント、downsampling 付きの制限オフラインバッファが実装済みです。再接続調整、50100 Node 負荷試験、署名付き Windows installer は今後の課題です。 `v0.1.0-alpha.14` はテスト版です。現在の開発ブランチには Windows client、Hub/Node installer、Links、mTLS、証明書失効と再登録、SQLite、監査、イベント、downsampling 付きの制限オフラインバッファが実装済みです。再接続調整、50100 Node 負荷試験、署名付き Windows installer は今後の課題です。
## ライセンス ## ライセンス

View file

@ -32,7 +32,7 @@ Hub에서 UDP `51820`과 TCP `7443`을 열고 Node 코드를 생성한 뒤 다
공유 root 암호가 없고 Node WireGuard 개인 키는 Node를 떠나지 않습니다. monitoring, terminal, Agent, Operator, AI automation identity는 분리됩니다. SSH는 shell/PTY/forwarding 없는 forced-command를 사용하고, mTLS는 역할을 제한하며, nftables는 명시된 Link만 허용합니다. SQLite는 방화벽 변경 전에 상태와 감사를 저장합니다. 공유 root 암호가 없고 Node WireGuard 개인 키는 Node를 떠나지 않습니다. monitoring, terminal, Agent, Operator, AI automation identity는 분리됩니다. SSH는 shell/PTY/forwarding 없는 forced-command를 사용하고, mTLS는 역할을 제한하며, nftables는 명시된 Link만 허용합니다. SQLite는 방화벽 변경 전에 상태와 감사를 저장합니다.
`v0.1.0-alpha.13`는 테스트 릴리스입니다. 현재 개발 branch에는 Windows client, Hub/Node installer, Links, mTLS, 인증서 폐기와 재등록, SQLite, 감사, event stream과 downsampling이 적용된 제한 offline buffer가 구현되었습니다. 재연결 조정, 50100 Node 부하 시험과 서명된 Windows installer가 남아 있습니다. `v0.1.0-alpha.14`는 테스트 릴리스입니다. 현재 개발 branch에는 Windows client, Hub/Node installer, Links, mTLS, 인증서 폐기와 재등록, SQLite, 감사, event stream과 downsampling이 적용된 제한 offline buffer가 구현되었습니다. 재연결 조정, 50100 Node 부하 시험과 서명된 Windows installer가 남아 있습니다.
## 라이선스 ## 라이선스

View file

@ -32,7 +32,7 @@ Abra UDP `51820` e TCP `7443` no Hub, gere códigos e instale os demais servidor
Não há senha root compartilhada e a chave WireGuard privada nunca sai do Node. As identidades de monitoramento, terminal, Agent, Operator e automação de IA são separadas. SSH usa forced-command sem shell/PTY/forwarding; mTLS restringe funções; nftables permite apenas Links explícitos; SQLite registra estado e auditoria antes da mudança no firewall. Não há senha root compartilhada e a chave WireGuard privada nunca sai do Node. As identidades de monitoramento, terminal, Agent, Operator e automação de IA são separadas. SSH usa forced-command sem shell/PTY/forwarding; mTLS restringe funções; nftables permite apenas Links explícitos; SQLite registra estado e auditoria antes da mudança no firewall.
`v0.1.0-alpha.13` é uma versão de teste. A branch atual contém cliente Windows, instalador Hub/Node, Links, mTLS, revogação e novo registro de certificados, SQLite, auditoria, eventos e buffer offline limitado com downsampling. Restam reconciliação, teste com 50100 Nodes e instalador Windows assinado. `v0.1.0-alpha.14` é uma versão de teste. A branch atual contém cliente Windows, instalador Hub/Node, Links, mTLS, revogação e novo registro de certificados, SQLite, auditoria, eventos e buffer offline limitado com downsampling. Restam reconciliação, teste com 50100 Nodes e instalador Windows assinado.
## Licença ## Licença

View file

@ -38,7 +38,7 @@ sudo ./ochenstarik-server-monitor-manager.sh hub
Нет общих root-паролей; приватный WireGuard-ключ Node не покидает Node. Идентичности monitoring, terminal, Agent, Operator и AI-автоматизации разделены. Monitoring SSH использует forced-command без shell/PTY/forwarding. Agent может отправлять heartbeat только своего узла, Operator управляет inventory и Links. Отключение Link сначала сохраняется в SQLite, затем удаляет разрешение nftables; повтор запроса не повторяет побочный эффект. Нет общих root-паролей; приватный WireGuard-ключ Node не покидает Node. Идентичности monitoring, terminal, Agent, Operator и AI-автоматизации разделены. Monitoring SSH использует forced-command без shell/PTY/forwarding. Agent может отправлять heartbeat только своего узла, Operator управляет inventory и Links. Отключение Link сначала сохраняется в SQLite, затем удаляет разрешение nftables; повтор запроса не повторяет побочный эффект.
`v0.1.0-alpha.13` предназначен для тестирования. В текущей ветке разработки уже готовы Windows SSH-monitoring, Hub/Node installer, Links, mTLS, отзыв и повторная регистрация сертификатов, SQLite, аудит, поток событий, ограниченный offline-буфер Agent с downsampling и долговечный reconnect reconciliation. Остались нагрузочный тест 50100 Node и подписанный Windows installer. До стабильного релиза используйте тестовые или резервируемые серверы. `v0.1.0-alpha.14` предназначен для тестирования. В текущей ветке разработки уже готовы Windows SSH-monitoring, Hub/Node installer, Links, mTLS, отзыв и повторная регистрация сертификатов, SQLite, аудит, поток событий, ограниченный offline-буфер Agent с downsampling и долговечный reconnect reconciliation. Остались нагрузочный тест 50100 Node и подписанный Windows installer. До стабильного релиза используйте тестовые или резервируемые серверы.
## Лицензия ## Лицензия

View file

@ -32,7 +32,7 @@ Hub üzerinde UDP `51820` ve TCP `7443` açın, Node kodlarını üretin ve diğ
Ortak root parolası yoktur ve Node'un WireGuard özel anahtarı Node'dan çıkmaz. Monitoring, terminal, Agent, Operator ve AI automation kimlikleri ayrıdır. SSH shell/PTY/forwarding vermeyen forced-command kullanır; mTLS rolleri sınırlar; nftables yalnızca açık Links'e izin verir; SQLite güvenlik duvarı değişmeden önce durum ve audit kaydeder. Ortak root parolası yoktur ve Node'un WireGuard özel anahtarı Node'dan çıkmaz. Monitoring, terminal, Agent, Operator ve AI automation kimlikleri ayrıdır. SSH shell/PTY/forwarding vermeyen forced-command kullanır; mTLS rolleri sınırlar; nftables yalnızca açık Links'e izin verir; SQLite güvenlik duvarı değişmeden önce durum ve audit kaydeder.
`v0.1.0-alpha.13` test sürümüdür. Güncel geliştirme dalında Windows client, Hub/Node installer, Links, mTLS, sertifika iptali ve yeniden kayıt, SQLite, audit, event stream ve downsampling kullanan sınırlı offline buffer hazırdır. Yeniden bağlantı uzlaştırması, 50100 Node yük testi ve imzalı Windows installer sıradadır. `v0.1.0-alpha.14` test sürümüdür. Güncel geliştirme dalında Windows client, Hub/Node installer, Links, mTLS, sertifika iptali ve yeniden kayıt, SQLite, audit, event stream ve downsampling kullanan sınırlı offline buffer hazırdır. Yeniden bağlantı uzlaştırması, 50100 Node yük testi ve imzalı Windows installer sıradadır.
## Lisans ## Lisans

View file

@ -34,7 +34,7 @@ sudo ./ochenstarik-server-monitor-manager.sh hub
系统不共享 root 密码Node 的 WireGuard 私钥不会离开本机。监控、终端、Agent、Operator 和 AI 自动化身份相互隔离。SSH 使用无 shell、PTY、转发权限的 forced-commandmTLS 限制角色nftables 仅允许明确 LinkSQLite 在执行防火墙变更前保存目标状态和审计。 系统不共享 root 密码Node 的 WireGuard 私钥不会离开本机。监控、终端、Agent、Operator 和 AI 自动化身份相互隔离。SSH 使用无 shell、PTY、转发权限的 forced-commandmTLS 限制角色nftables 仅允许明确 LinkSQLite 在执行防火墙变更前保存目标状态和审计。
`v0.1.0-alpha.13` 是测试版。当前开发分支已包含 Windows 客户端、Hub/Node 安装器、Links、mTLS、证书撤销与重新注册、SQLite、审计、事件流以及带降采样的有限离线缓冲。待完成重连协调、50100 Node 压测和签名 Windows 安装器。 `v0.1.0-alpha.14` 是测试版。当前开发分支已包含 Windows 客户端、Hub/Node 安装器、Links、mTLS、证书撤销与重新注册、SQLite、审计、事件流以及带降采样的有限离线缓冲。待完成重连协调、50100 Node 压测和签名 Windows 安装器。
## 许可证 ## 许可证

View file

@ -10,9 +10,14 @@ Server Monitor Manager устанавливает Control (Hub) и Agent (Node)
- `ochenstarik-server-monitor-manager.sh` и `.sha256`; - `ochenstarik-server-monitor-manager.sh` и `.sha256`;
- `server-monitor-manager-linux-x64.tar.gz` или `server-monitor-manager-linux-arm64.tar.gz`; - `server-monitor-manager-linux-x64.tar.gz` или `server-monitor-manager-linux-arm64.tar.gz`;
- соответствующий `.tar.gz.sha256`. - соответствующий `.tar.gz.sha256`;
- `server-monitor-manager-manifest.json`, `server-monitor-manager-manifest.sig` и `server-monitor-manager-manifest.pem`.
Bootstrap проверяет SHA-256 до распаковки и принимает в архиве только каталоги `agent`, `control`, `deploy` и `bootstrap`. Все файлы должны лежать в одном каталоге: `verify-release` ищет manifest, подпись и сертификат рядом с архивом. Без любого из трёх проверка отказывает и предлагает явный обход `SMM_ALLOW_UNSIGNED=1` — он предназначен только для сборок, выпущенных до появления подписи, и в обычной установке не используется.
Сертификат нужен потому, что manifest подписывается keyless-режимом cosign: подпись проверяется эфемерным сертификатом, привязанным к workflow выпуска, а не постоянным ключом.
Bootstrap проверяет подпись manifest, затем SHA-256 архива по manifest, и принимает в архиве только каталоги `agent`, `control`, `deploy` и `bootstrap`.
## 1. Установка главного сервера (Hub) ## 1. Установка главного сервера (Hub)

View file

@ -13,5 +13,6 @@ Known release history:
- `v0.1.0-alpha.8` contains the v1 `server-monitor-manager-bootstrap-manifest.json` layout and an orphaned `server-monitor-manager-manifest.sig` without the corresponding manifest v2. Published assets remain immutable; the anomaly is documented rather than repaired in place. - `v0.1.0-alpha.8` contains the v1 `server-monitor-manager-bootstrap-manifest.json` layout and an orphaned `server-monitor-manager-manifest.sig` without the corresponding manifest v2. Published assets remain immutable; the anomaly is documented rather than repaired in place.
- Tags `v0.1.0-alpha.10` and `v0.1.0-alpha.11` exist, but their Release pipelines failed before a GitHub Release was published. Those version numbers are burned and must not be moved, deleted, recreated, or reused. - Tags `v0.1.0-alpha.10` and `v0.1.0-alpha.11` exist, but their Release pipelines failed before a GitHub Release was published. Those version numbers are burned and must not be moved, deleted, recreated, or reused.
- `v0.1.0-alpha.12` was published, but its Windows `SHA256SUMS` asset used CRLF and was not consumable by GNU `sha256sum -c`. The immutable release remains published as historical evidence; the correction is released under a higher version. - `v0.1.0-alpha.12` was published, but its Windows `SHA256SUMS` asset used CRLF and was not consumable by GNU `sha256sum -c`. The immutable release remains published as historical evidence; the correction is released under a higher version.
- `v0.1.0-alpha.13` corrected the checksum portability defect, but its keyless manifest signature lacked the published signing certificate required by production consumers. The immutable release remains published as historical evidence; the producer/consumer certificate contract is corrected under a higher version.
Every release candidate must pass a branch `workflow_dispatch` run of the Release pipeline before its immutable version tag is created. The release owner has sole write ownership of version sources, `deploy/**`, `tests/bootstrap/**`, release workflows, the root README release status, and translated README release statuses. Other contributors request changes to those paths in their report; they do not edit or bump them directly. One pull request covers one release topic and may merge only after required CI is green. Every release candidate must pass a branch `workflow_dispatch` run of the Release pipeline before its immutable version tag is created. The release owner has sole write ownership of version sources, `deploy/**`, `tests/bootstrap/**`, release workflows, the root README release status, and translated README release statuses. Other contributors request changes to those paths in their report; they do not edit or bump them directly. One pull request covers one release topic and may merge only after required CI is green.

View file

@ -22,7 +22,7 @@ public interface IHttpTransport
public interface ISignatureVerifier public interface ISignatureVerifier
{ {
Task VerifySignatureAsync(string signaturePath, string manifestPath, CancellationToken cancellationToken = default); Task VerifySignatureAsync(string signaturePath, string manifestPath, string certificatePath, CancellationToken cancellationToken = default);
} }
public interface IFileStorage public interface IFileStorage
@ -88,7 +88,12 @@ public class ProcessSignatureVerifier : ISignatureVerifier
_httpTransport = httpTransport; _httpTransport = httpTransport;
} }
public async Task VerifySignatureAsync(string signaturePath, string manifestPath, CancellationToken cancellationToken = default) internal static string BuildVerificationArguments(string signaturePath, string manifestPath, string certificatePath)
{
return $"verify-blob --certificate \"{certificatePath}\" --certificate-oidc-issuer \"{OidcIssuer}\" --certificate-identity-regexp \"{OidcIdentityRegexp}\" --signature \"{signaturePath}\" \"{manifestPath}\"";
}
public async Task VerifySignatureAsync(string signaturePath, string manifestPath, string certificatePath, CancellationToken cancellationToken = default)
{ {
var cosignPath = Path.Combine(_fileStorage.GetTempFolder(), "cosign.exe"); var cosignPath = Path.Combine(_fileStorage.GetTempFolder(), "cosign.exe");
if (!_fileStorage.FileExists(cosignPath) || !VerifyFileHash(cosignPath, CosignHash)) if (!_fileStorage.FileExists(cosignPath) || !VerifyFileHash(cosignPath, CosignHash))
@ -107,7 +112,7 @@ public class ProcessSignatureVerifier : ISignatureVerifier
StartInfo = new ProcessStartInfo StartInfo = new ProcessStartInfo
{ {
FileName = cosignPath, FileName = cosignPath,
Arguments = $"verify-blob --certificate-oidc-issuer \"{OidcIssuer}\" --certificate-identity-regexp \"{OidcIdentityRegexp}\" --signature \"{signaturePath}\" \"{manifestPath}\"", Arguments = BuildVerificationArguments(signaturePath, manifestPath, certificatePath),
UseShellExecute = false, UseShellExecute = false,
RedirectStandardError = true, RedirectStandardError = true,
RedirectStandardOutput = true, RedirectStandardOutput = true,
@ -217,20 +222,24 @@ public class UpdateService
var manifestUrl = assets.FirstOrDefault(a => a?["name"]?.GetValue<string>() == "server-monitor-manager-manifest.json")?["browser_download_url"]?.GetValue<string>(); var manifestUrl = assets.FirstOrDefault(a => a?["name"]?.GetValue<string>() == "server-monitor-manager-manifest.json")?["browser_download_url"]?.GetValue<string>();
var sigUrl = assets.FirstOrDefault(a => a?["name"]?.GetValue<string>() == "server-monitor-manager-manifest.sig")?["browser_download_url"]?.GetValue<string>(); var sigUrl = assets.FirstOrDefault(a => a?["name"]?.GetValue<string>() == "server-monitor-manager-manifest.sig")?["browser_download_url"]?.GetValue<string>();
var certificateUrl = assets.FirstOrDefault(a => a?["name"]?.GetValue<string>() == "server-monitor-manager-manifest.pem")?["browser_download_url"]?.GetValue<string>();
if (manifestUrl is null || sigUrl is null) if (manifestUrl is null || sigUrl is null || certificateUrl is null)
{ {
throw new InvalidOperationException("Manifest or signature not found in the release. Update rejected."); throw new InvalidOperationException("Manifest, signature, or certificate not found in the release. Update rejected.");
} }
// Validate URLs belong to the same tag! // Validate URLs belong to the same tag!
if (!manifestUrl.Contains($"/releases/download/{releaseTagName}/") || !sigUrl.Contains($"/releases/download/{releaseTagName}/")) if (!manifestUrl.Contains($"/releases/download/{releaseTagName}/") ||
!sigUrl.Contains($"/releases/download/{releaseTagName}/") ||
!certificateUrl.Contains($"/releases/download/{releaseTagName}/"))
{ {
throw new InvalidOperationException("Manifest or signature URL does not match the release tag. Update rejected."); throw new InvalidOperationException("Manifest, signature, or certificate URL does not match the release tag. Update rejected.");
} }
var manifestJson = await _http.GetStringAsync(manifestUrl, cancellationToken); var manifestJson = await _http.GetStringAsync(manifestUrl, cancellationToken);
var manifestSig = await _http.GetStringAsync(sigUrl, cancellationToken); var manifestSig = await _http.GetStringAsync(sigUrl, cancellationToken);
var manifestCertificate = await _http.GetStringAsync(certificateUrl, cancellationToken);
var manifestNode = JsonNode.Parse(manifestJson); var manifestNode = JsonNode.Parse(manifestJson);
if (manifestNode is null) if (manifestNode is null)
@ -257,11 +266,13 @@ public class UpdateService
var tempFolder = _fileStorage.GetTempFolder(); var tempFolder = _fileStorage.GetTempFolder();
var manifestPath = Path.Combine(tempFolder, "server-monitor-manager-manifest.json"); var manifestPath = Path.Combine(tempFolder, "server-monitor-manager-manifest.json");
var sigPath = Path.Combine(tempFolder, "server-monitor-manager-manifest.sig"); var sigPath = Path.Combine(tempFolder, "server-monitor-manager-manifest.sig");
var certificatePath = Path.Combine(tempFolder, "server-monitor-manager-manifest.pem");
await _fileStorage.WriteAllTextAsync(manifestPath, manifestJson, cancellationToken); await _fileStorage.WriteAllTextAsync(manifestPath, manifestJson, cancellationToken);
await _fileStorage.WriteAllTextAsync(sigPath, manifestSig, cancellationToken); await _fileStorage.WriteAllTextAsync(sigPath, manifestSig, cancellationToken);
await _fileStorage.WriteAllTextAsync(certificatePath, manifestCertificate, cancellationToken);
Log.TraceEvent(TraceEventType.Information, 0, "Verifying manifest signature..."); Log.TraceEvent(TraceEventType.Information, 0, "Verifying manifest signature...");
await _signatureVerifier.VerifySignatureAsync(sigPath, manifestPath, cancellationToken); await _signatureVerifier.VerifySignatureAsync(sigPath, manifestPath, certificatePath, cancellationToken);
Log.TraceEvent(TraceEventType.Information, 0, "Manifest signature verified successfully."); Log.TraceEvent(TraceEventType.Information, 0, "Manifest signature verified successfully.");
var msixUrl = assets.FirstOrDefault(a => a?["name"]?.GetValue<string>() == "ServerMonitorManager-win-x64.msix")?["browser_download_url"]?.GetValue<string>(); var msixUrl = assets.FirstOrDefault(a => a?["name"]?.GetValue<string>() == "ServerMonitorManager-win-x64.msix")?["browser_download_url"]?.GetValue<string>();

View file

@ -21,8 +21,8 @@ namespace ServerMonitorManager.Desktop.Security.Tests
public class MockSignatureVerifier : ISignatureVerifier public class MockSignatureVerifier : ISignatureVerifier
{ {
public Func<string, string, Task> VerifySignatureAsyncFunc { get; set; } = (_, _) => Task.CompletedTask; public Func<string, string, string, Task> VerifySignatureAsyncFunc { get; set; } = (_, _, _) => Task.CompletedTask;
public Task VerifySignatureAsync(string signaturePath, string manifestPath, CancellationToken cancellationToken = default) => VerifySignatureAsyncFunc(signaturePath, manifestPath); public Task VerifySignatureAsync(string signaturePath, string manifestPath, string certificatePath, CancellationToken cancellationToken = default) => VerifySignatureAsyncFunc(signaturePath, manifestPath, certificatePath);
} }
public class MockFileStorage : IFileStorage public class MockFileStorage : IFileStorage
@ -45,6 +45,7 @@ namespace ServerMonitorManager.Desktop.Security.Tests
""assets"": [ ""assets"": [
{ ""name"": ""server-monitor-manager-manifest.json"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/server-monitor-manager-manifest.json"" }, { ""name"": ""server-monitor-manager-manifest.json"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/server-monitor-manager-manifest.json"" },
{ ""name"": ""server-monitor-manager-manifest.sig"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/server-monitor-manager-manifest.sig"" }, { ""name"": ""server-monitor-manager-manifest.sig"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/server-monitor-manager-manifest.sig"" },
{ ""name"": ""server-monitor-manager-manifest.pem"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/server-monitor-manager-manifest.pem"" },
{ ""name"": ""ServerMonitorManager-win-x64.msix"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/ServerMonitorManager-win-x64.msix"" } { ""name"": ""ServerMonitorManager-win-x64.msix"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/ServerMonitorManager-win-x64.msix"" }
] ]
}"; }";
@ -59,6 +60,16 @@ namespace ServerMonitorManager.Desktop.Security.Tests
] ]
}"; }";
private const string NoCertificateReleaseJson = @"
{
""tag_name"": ""v0.1.0-alpha.9"",
""assets"": [
{ ""name"": ""server-monitor-manager-manifest.json"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/server-monitor-manager-manifest.json"" },
{ ""name"": ""server-monitor-manager-manifest.sig"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/server-monitor-manager-manifest.sig"" },
{ ""name"": ""ServerMonitorManager-win-x64.msix"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/ServerMonitorManager-win-x64.msix"" }
]
}";
private const string ValidManifestJson = @" private const string ValidManifestJson = @"
{ {
""version"": ""v0.1.0-alpha.9"", ""version"": ""v0.1.0-alpha.9"",
@ -101,7 +112,7 @@ namespace ServerMonitorManager.Desktop.Security.Tests
var http = new MockHttpTransport { GetStringAsyncFunc = url => Task.FromResult(url.EndsWith("releases/latest") ? ValidReleaseJson : ValidManifestJson) }; var http = new MockHttpTransport { GetStringAsyncFunc = url => Task.FromResult(url.EndsWith("releases/latest") ? ValidReleaseJson : ValidManifestJson) };
var verifier = new MockSignatureVerifier var verifier = new MockSignatureVerifier
{ {
VerifySignatureAsyncFunc = (_, _) => throw new InvalidOperationException("Signature verification failed: identity mismatch") VerifySignatureAsyncFunc = (_, _, _) => throw new InvalidOperationException("Signature verification failed: identity mismatch")
}; };
var storage = new MockFileStorage(); var storage = new MockFileStorage();
@ -144,7 +155,7 @@ namespace ServerMonitorManager.Desktop.Security.Tests
var service = new UpdateService(http, new MockSignatureVerifier(), new MockFileStorage()); var service = new UpdateService(http, new MockSignatureVerifier(), new MockFileStorage());
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.CheckForUpdatesAsync()); var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.CheckForUpdatesAsync());
Assert.Contains("Manifest or signature not found", ex.Message); Assert.Contains("signature", ex.Message, StringComparison.OrdinalIgnoreCase);
} }
[Fact] [Fact]
@ -153,7 +164,7 @@ namespace ServerMonitorManager.Desktop.Security.Tests
var http = new MockHttpTransport { GetStringAsyncFunc = url => Task.FromResult(url.EndsWith("releases/latest") ? ValidReleaseJson : ValidManifestJson) }; var http = new MockHttpTransport { GetStringAsyncFunc = url => Task.FromResult(url.EndsWith("releases/latest") ? ValidReleaseJson : ValidManifestJson) };
var verifier = new MockSignatureVerifier var verifier = new MockSignatureVerifier
{ {
VerifySignatureAsyncFunc = (_, _) => throw new InvalidOperationException("Signature verification failed: invalid signature format") VerifySignatureAsyncFunc = (_, _, _) => throw new InvalidOperationException("Signature verification failed: invalid signature format")
}; };
var storage = new MockFileStorage(); var storage = new MockFileStorage();
@ -169,7 +180,7 @@ namespace ServerMonitorManager.Desktop.Security.Tests
bool signatureVerified = false; bool signatureVerified = false;
var verifier = new MockSignatureVerifier var verifier = new MockSignatureVerifier
{ {
VerifySignatureAsyncFunc = (_, _) => { signatureVerified = true; return Task.CompletedTask; } VerifySignatureAsyncFunc = (_, _, _) => { signatureVerified = true; return Task.CompletedTask; }
}; };
var storage = new MockFileStorage(); var storage = new MockFileStorage();
@ -246,7 +257,8 @@ namespace ServerMonitorManager.Desktop.Security.Tests
""tag_name"": ""v0.1.0-alpha.9"", ""tag_name"": ""v0.1.0-alpha.9"",
""assets"": [ ""assets"": [
{ ""name"": ""server-monitor-manager-manifest.json"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/server-monitor-manager-manifest.json"" }, { ""name"": ""server-monitor-manager-manifest.json"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/server-monitor-manager-manifest.json"" },
{ ""name"": ""server-monitor-manager-manifest.sig"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/server-monitor-manager-manifest.sig"" } { ""name"": ""server-monitor-manager-manifest.sig"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/server-monitor-manager-manifest.sig"" },
{ ""name"": ""server-monitor-manager-manifest.pem"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/server-monitor-manager-manifest.pem"" }
] ]
}"; }";
var http = new MockHttpTransport var http = new MockHttpTransport
@ -264,5 +276,25 @@ namespace ServerMonitorManager.Desktop.Security.Tests
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.CheckForUpdatesAsync()); var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.CheckForUpdatesAsync());
Assert.Contains("MSIX", ex.Message, StringComparison.OrdinalIgnoreCase); Assert.Contains("MSIX", ex.Message, StringComparison.OrdinalIgnoreCase);
} }
[Fact]
public async Task MissingCertificateAsset_IsRejected()
{
var http = new MockHttpTransport { GetStringAsyncFunc = _ => Task.FromResult(NoCertificateReleaseJson) };
var service = new UpdateService(http, new MockSignatureVerifier(), new MockFileStorage());
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.CheckForUpdatesAsync());
Assert.Contains("certificate", ex.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void ProductionVerifierArguments_PinCertificateIssuerAndReleaseWorkflow()
{
var arguments = ProcessSignatureVerifier.BuildVerificationArguments("manifest.sig", "manifest.json", "manifest.pem");
Assert.Contains("--certificate \"manifest.pem\"", arguments, StringComparison.Ordinal);
Assert.Contains("--certificate-oidc-issuer \"https://token.actions.githubusercontent.com\"", arguments, StringComparison.Ordinal);
Assert.Contains("linux-release\\.yml@refs/tags/v.*$", arguments, StringComparison.Ordinal);
}
} }
} }

View file

@ -791,10 +791,11 @@ cat >"$fixture/server-monitor-manager-manifest.json" <<MEOF
} }
} }
MEOF MEOF
printf '%s\n' 'test-key certificate placeholder' >"$fixture/server-monitor-manager-manifest.pem"
if command -v cosign &>/dev/null; then if command -v cosign &>/dev/null; then
COSIGN_PASSWORD="" cosign generate-key-pair --output-key-prefix="$fixture/contract-test" COSIGN_PASSWORD="" cosign generate-key-pair --output-key-prefix="$fixture/contract-test"
COSIGN_PASSWORD="" cosign sign-blob --yes --key "$fixture/contract-test.key" \ COSIGN_PASSWORD="" cosign sign-blob --yes --tlog-upload=false --key "$fixture/contract-test.key" \
--output-signature "$fixture/server-monitor-manager-manifest.sig" \ --output-signature "$fixture/server-monitor-manager-manifest.sig" \
"$fixture/server-monitor-manager-manifest.json" "$fixture/server-monitor-manager-manifest.json"
SMM_TEST_PUBKEY="$fixture/contract-test.pub" \ SMM_TEST_PUBKEY="$fixture/contract-test.pub" \
@ -814,7 +815,7 @@ cat >"$fixture/server-monitor-manager-manifest.json" <<MEOF
} }
MEOF MEOF
if command -v cosign &>/dev/null; then if command -v cosign &>/dev/null; then
COSIGN_PASSWORD="" cosign sign-blob --yes --key "$fixture/contract-test.key" \ COSIGN_PASSWORD="" cosign sign-blob --yes --tlog-upload=false --key "$fixture/contract-test.key" \
--output-signature "$fixture/server-monitor-manager-manifest.sig" \ --output-signature "$fixture/server-monitor-manager-manifest.sig" \
"$fixture/server-monitor-manager-manifest.json" "$fixture/server-monitor-manager-manifest.json"
if SMM_TEST_PUBKEY="$fixture/contract-test.pub" \ if SMM_TEST_PUBKEY="$fixture/contract-test.pub" \

View file

@ -37,10 +37,11 @@ cat <<EOF > server-monitor-manager-manifest.json
EOF EOF
cosign sign-blob --yes --tlog-upload=false --key cosign.key --output-signature server-monitor-manager-manifest.sig server-monitor-manager-manifest.json cosign sign-blob --yes --tlog-upload=false --key cosign.key --output-signature server-monitor-manager-manifest.sig server-monitor-manager-manifest.json
CLEANUP_FILES+=(server-monitor-manager-manifest.json server-monitor-manager-manifest.sig) printf '%s\n' 'test-key certificate placeholder' >server-monitor-manager-manifest.pem
CLEANUP_FILES+=(server-monitor-manager-manifest.json server-monitor-manager-manifest.sig server-monitor-manager-manifest.pem)
echo "Test 1: Valid signature and hash" echo "Test 1: Valid signature and hash"
if ! bash deploy/ochenstarik-server-monitor-manager.sh verify-manifest server-monitor-manager-manifest.json server-monitor-manager-manifest.sig; then if ! bash deploy/ochenstarik-server-monitor-manager.sh verify-manifest server-monitor-manager-manifest.json server-monitor-manager-manifest.sig server-monitor-manager-manifest.pem; then
echo "FAIL: Valid payload rejected" echo "FAIL: Valid payload rejected"
exit 1 exit 1
fi fi
@ -67,25 +68,41 @@ cat <<EOF > server-monitor-manager-manifest.json
} }
} }
EOF EOF
if bash deploy/ochenstarik-server-monitor-manager.sh verify-manifest server-monitor-manager-manifest.json server-monitor-manager-manifest.sig >/dev/null 2>&1; then if bash deploy/ochenstarik-server-monitor-manager.sh verify-manifest server-monitor-manager-manifest.json server-monitor-manager-manifest.sig server-monitor-manager-manifest.pem >/dev/null 2>&1; then
echo "FAIL: Substituted hash accepted" echo "FAIL: Substituted hash accepted"
exit 1 exit 1
fi fi
echo "PASS: Substituted hash rejected" echo "PASS: Substituted hash rejected"
echo "Test 4: Manifest without signature" echo "Test 4: Manifest without signature"
if bash deploy/ochenstarik-server-monitor-manager.sh verify-manifest server-monitor-manager-manifest.json "" >/dev/null 2>&1; then if bash deploy/ochenstarik-server-monitor-manager.sh verify-manifest server-monitor-manager-manifest.json "" server-monitor-manager-manifest.pem >/dev/null 2>&1; then
echo "FAIL: Missing signature accepted" echo "FAIL: Missing signature accepted"
exit 1 exit 1
fi fi
echo "PASS: Missing signature rejected" echo "PASS: Missing signature rejected"
echo "Test 5: Synthetic pre-alpha.9 v1 release layout" echo "Test 5: Manifest without certificate"
unset SMM_TEST_PUBKEY
if output="$(bash deploy/ochenstarik-server-monitor-manager.sh verify-manifest server-monitor-manager-manifest.json server-monitor-manager-manifest.sig "" 2>&1)"; then
echo "FAIL: Missing certificate accepted"
exit 1
fi
if [[ "$output" != *"Certificate not found"* ]]; then
printf 'FAIL: Missing certificate rejection lacked diagnostic. Output: %s\n' "$output" >&2
exit 1
fi
export SMM_TEST_PUBKEY="cosign.pub"
echo "PASS: Missing certificate rejected with diagnostic"
echo "Test 6: Synthetic pre-alpha.9 v1 release layout"
fixture_root="tests/fixtures/alpha8-v1-release" fixture_root="tests/fixtures/alpha8-v1-release"
fixture_work="$(mktemp -d -t smm-alpha8-v1.XXXXXXXX)" fixture_work="$(mktemp -d -t smm-alpha8-v1.XXXXXXXX)"
CLEANUP_DIRS+=("$fixture_work") CLEANUP_DIRS+=("$fixture_work")
cp "$fixture_root/server-monitor-manager-bootstrap-manifest.json" "$fixture_work/" cp "$fixture_root/server-monitor-manager-bootstrap-manifest.json" "$fixture_work/"
cp -R "$fixture_root/archive-root" "$fixture_work/archive-root" cp -R "$fixture_root/archive-root" "$fixture_work/archive-root"
# Git for Windows may materialize text fixtures with CRLF. The historical
# manifest hash pins the canonical LF payload used to build the local archive.
perl -pi -e 's/\x0D$//' "$fixture_work/archive-root/bootstrap/ochenstarik-server-monitor-manager.sh"
expected_bootstrap_hash="$(sed -n 's/.*"bootstrap_sha256": "\([0-9a-f]\{64\}\)".*/\1/p' "$fixture_work/server-monitor-manager-bootstrap-manifest.json")" expected_bootstrap_hash="$(sed -n 's/.*"bootstrap_sha256": "\([0-9a-f]\{64\}\)".*/\1/p' "$fixture_work/server-monitor-manager-bootstrap-manifest.json")"
actual_bootstrap_hash="$(sha256sum "$fixture_work/archive-root/bootstrap/ochenstarik-server-monitor-manager.sh" | awk '{print $1}')" actual_bootstrap_hash="$(sha256sum "$fixture_work/archive-root/bootstrap/ochenstarik-server-monitor-manager.sh" | awk '{print $1}')"
[[ "$expected_bootstrap_hash" == "$actual_bootstrap_hash" ]] || { [[ "$expected_bootstrap_hash" == "$actual_bootstrap_hash" ]] || {
@ -103,7 +120,7 @@ if SMM_ALLOW_UNSIGNED=0 bash deploy/ochenstarik-server-monitor-manager.sh verify
echo "FAIL: Unsigned v1 fixture accepted without explicit bypass" echo "FAIL: Unsigned v1 fixture accepted without explicit bypass"
exit 1 exit 1
fi fi
if ! grep -Fq 'Manifest and signature are required' "$fixture_work/strict.out"; then if ! grep -Fq 'Manifest, signature, and certificate are required' "$fixture_work/strict.out"; then
echo "FAIL: Strict v1 rejection lacked expected diagnostic" echo "FAIL: Strict v1 rejection lacked expected diagnostic"
cat "$fixture_work/strict.out" >&2 cat "$fixture_work/strict.out" >&2
exit 1 exit 1

View file

@ -16,7 +16,7 @@ v1_fixture="$root/tests/fixtures/alpha8-v1-release"
exit 1 exit 1
} }
bash -n "$setup" bash -n "$setup"
grep -Fq 'readonly DEFAULT_RELEASE_TAG="v0.1.0-alpha.13"' "$setup" grep -Fq 'readonly DEFAULT_RELEASE_TAG="v0.1.0-alpha.14"' "$setup"
if grep -Fq 'validate_control_url' "$setup" || grep -Fq '${CONTROL_URL%/}/control' "$setup"; then if grep -Fq 'validate_control_url' "$setup" || grep -Fq '${CONTROL_URL%/}/control' "$setup"; then
printf '%s\n' 'temporary control URL workaround must not be present in smm-setup.sh' >&2 printf '%s\n' 'temporary control URL workaround must not be present in smm-setup.sh' >&2
exit 1 exit 1
@ -51,6 +51,9 @@ grep -Fq 'tests/bootstrap/**' "$policy"
grep -Fq 'v0.1.0-alpha.12' "$policy" grep -Fq 'v0.1.0-alpha.12' "$policy"
grep -Fq "perl -pi -e 's/\\x0D$//' \"\$windows_dir/SHA256SUMS\"" "$workflow" grep -Fq "perl -pi -e 's/\\x0D$//' \"\$windows_dir/SHA256SUMS\"" "$workflow"
grep -Fq 'sha256sum -c SHA256SUMS' "$workflow" grep -Fq 'sha256sum -c SHA256SUMS' "$workflow"
grep -Fq -- '--output-certificate server-monitor-manager-manifest.pem' "$workflow"
grep -Fq 'server-monitor-manager-manifest.pem' "$workflow"
grep -Fq 'v0.1.0-alpha.13' "$policy"
grep -Fq 'Published release tags and their assets are immutable.' "$installer_contract" grep -Fq 'Published release tags and their assets are immutable.' "$installer_contract"
grep -Fq 'publish a new, higher version tag' "$installer_contract" grep -Fq 'publish a new, higher version tag' "$installer_contract"
@ -62,7 +65,12 @@ grep -Fq 'server-monitor-manager-bootstrap-manifest.json' "$manifest_test"
grep -Fq 'SMM_ALLOW_UNSIGNED=1' "$manifest_test" grep -Fq 'SMM_ALLOW_UNSIGNED=1' "$manifest_test"
grep -Fq 'SMM_ALLOW_UNSIGNED=0' "$manifest_test" grep -Fq 'SMM_ALLOW_UNSIGNED=0' "$manifest_test"
grep -Fq -- '--tlog-upload=false' "$manifest_test" grep -Fq -- '--tlog-upload=false' "$manifest_test"
grep -Fq -- '--tlog-upload=false' "$root/tests/bootstrap/test-bootstrap-contract.sh"
grep -Fq 'server-monitor-manager-manifest.pem' "$root/tests/bootstrap/test-bootstrap-contract.sh"
grep -Fq -- '--insecure-ignore-tlog' "$root/deploy/ochenstarik-server-monitor-manager.sh" grep -Fq -- '--insecure-ignore-tlog' "$root/deploy/ochenstarik-server-monitor-manager.sh"
grep -Fq 'verify_args=(--certificate "$certificate" --certificate-oidc-issuer "$COSIGN_ISSUER" --certificate-identity-regexp "$COSIGN_IDENTITY_REGEXP")' "$root/deploy/ochenstarik-server-monitor-manager.sh"
grep -Fq 'verify-manifest MANIFEST SIGNATURE CERTIFICATE' "$root/deploy/ochenstarik-server-monitor-manager.sh"
grep -Fq 'verify-manifest requires MANIFEST SIGNATURE CERTIFICATE' "$root/deploy/ochenstarik-server-monitor-manager.sh"
[[ -s "$v1_fixture/server-monitor-manager-bootstrap-manifest.json" ]] [[ -s "$v1_fixture/server-monitor-manager-bootstrap-manifest.json" ]]
[[ -d "$v1_fixture/archive-root" ]] [[ -d "$v1_fixture/archive-root" ]]
grep -Fq '"schema": "smm-bootstrap-manifest/v1"' "$v1_fixture/server-monitor-manager-bootstrap-manifest.json" grep -Fq '"schema": "smm-bootstrap-manifest/v1"' "$v1_fixture/server-monitor-manager-bootstrap-manifest.json"
@ -112,7 +120,7 @@ chmod +x "$work/bin/curl"
HOME="$work/home" PATH="$work/bin:$PATH" bash "$setup" version >"$work/output" HOME="$work/home" PATH="$work/bin:$PATH" bash "$setup" version >"$work/output"
grep -Fq 'INNER_COMMAND=version' "$work/output" grep -Fq 'INNER_COMMAND=version' "$work/output"
grep -Fq '/releases/download/v0.1.0-alpha.13/ochenstarik-server-monitor-manager.sh' "$work/urls" grep -Fq '/releases/download/v0.1.0-alpha.14/ochenstarik-server-monitor-manager.sh' "$work/urls"
grep -Fq '/releases/download/v0.1.0-alpha.13/ochenstarik-server-monitor-manager.sh.sha256' "$work/urls" grep -Fq '/releases/download/v0.1.0-alpha.14/ochenstarik-server-monitor-manager.sh.sha256' "$work/urls"
printf '%s\n' 'RELEASE_CONTRACT=PASS' printf '%s\n' 'RELEASE_CONTRACT=PASS'

View file

@ -1,66 +1,94 @@
#!/bin/bash #!/bin/bash
# Every tampering scenario an operator could hit must be rejected by the release
# artefacts themselves. Downloads use public curl only: gh needs git context that
# the isolated workspace removes, and the operator has neither gh nor a token.
set -euo pipefail set -euo pipefail
TAG="${1:-}" TAG="${1:-}"
REPOSITORY="${SMM_REPOSITORY:-ochenstarik-ui/server-monitor-manager}"
if [[ -z "$TAG" ]]; then if [[ -z "$TAG" ]]; then
echo "Usage: $0 <tag>" echo "Usage: $0 <tag>" >&2
exit 1 exit 1
fi fi
BASE_URL="https://github.com/${REPOSITORY}/releases/download/${TAG}"
echo "Running negative tests against release $TAG..." echo "Running negative tests against release $TAG..."
# We will need smm-setup.sh or ochenstarik-server-monitor-manager.sh download() {
# We'll download ochenstarik-server-monitor-manager.sh directly to test verify-release local name="$1"
gh release download "$TAG" -p 'ochenstarik-server-monitor-manager.sh' curl --fail --silent --show-error --location --retry 3 \
chmod +x ochenstarik-server-monitor-manager.sh -o "$name" "${BASE_URL}/${name}" \
|| { echo "FAIL: asset is not downloadable: $name" >&2; exit 1; }
}
ARCHIVE="server-monitor-manager-linux-$(uname -m | sed -e 's/x86_64/x64/' -e 's/aarch64/arm64/').tar.gz" case "$(uname -m)" in
gh release download "$TAG" -p "$ARCHIVE" x86_64) RUNTIME="linux-x64" ;;
gh release download "$TAG" -p "server-monitor-manager-manifest.json" aarch64|arm64) RUNTIME="linux-arm64" ;;
gh release download "$TAG" -p "server-monitor-manager-manifest.sig" *) echo "FAIL: unsupported architecture $(uname -m)" >&2; exit 1 ;;
esac
ARCHIVE="server-monitor-manager-${RUNTIME}.tar.gz"
download ochenstarik-server-monitor-manager.sh
chmod +x ochenstarik-server-monitor-manager.sh
download "$ARCHIVE"
download "$ARCHIVE.sha256"
download server-monitor-manager-manifest.json
download server-monitor-manager-manifest.sig
download server-monitor-manager-manifest.pem
echo "Test 1: Altered byte in archive" echo "Test 1: Altered byte in archive"
cp "$ARCHIVE" "corrupted-$ARCHIVE" cp "$ARCHIVE" "corrupted-$ARCHIVE"
cp "$ARCHIVE.sha256" "corrupted-$ARCHIVE.sha256"
echo "corrupted" >>"corrupted-$ARCHIVE" echo "corrupted" >>"corrupted-$ARCHIVE"
if ./ochenstarik-server-monitor-manager.sh verify-release "corrupted-$ARCHIVE" >/dev/null 2>&1; then if ./ochenstarik-server-monitor-manager.sh verify-release "corrupted-$ARCHIVE" >/dev/null 2>&1; then
echo "FAIL: Altered archive was accepted!" echo "FAIL: Altered archive was accepted!" >&2
exit 1 exit 1
fi fi
echo "PASS: Altered archive rejected." echo "PASS: Altered archive rejected."
rm "corrupted-$ARCHIVE" rm -f "corrupted-$ARCHIVE" "corrupted-$ARCHIVE.sha256"
echo "Test 2: Substituted hash in manifest without resigning" echo "Test 2: Substituted hash in manifest without resigning"
cp server-monitor-manager-manifest.json corrupted-manifest.json cp server-monitor-manager-manifest.json corrupted-manifest.json
# Replace all hashes with zeros
sed -i 's/"[a-f0-9]\{64\}"/"0000000000000000000000000000000000000000000000000000000000000000"/g' corrupted-manifest.json sed -i 's/"[a-f0-9]\{64\}"/"0000000000000000000000000000000000000000000000000000000000000000"/g' corrupted-manifest.json
# Test verify-manifest directly if ./ochenstarik-server-monitor-manager.sh verify-manifest corrupted-manifest.json \
if ./ochenstarik-server-monitor-manager.sh verify-manifest corrupted-manifest.json server-monitor-manager-manifest.sig >/dev/null 2>&1; then server-monitor-manager-manifest.sig server-monitor-manager-manifest.pem >/dev/null 2>&1; then
echo "FAIL: Manifest with substituted hash accepted!" echo "FAIL: Manifest with substituted hash accepted!" >&2
exit 1 exit 1
fi fi
echo "PASS: Substituted hash rejected." echo "PASS: Substituted hash rejected."
rm corrupted-manifest.json rm -f corrupted-manifest.json
echo "Test 3: Manifest without signature" echo "Test 3: Manifest without signature"
# We just pass an empty string for the signature file argument if ./ochenstarik-server-monitor-manager.sh verify-manifest server-monitor-manager-manifest.json \
if ./ochenstarik-server-monitor-manager.sh verify-manifest server-monitor-manager-manifest.json "" >/dev/null 2>&1; then "" server-monitor-manager-manifest.pem >/dev/null 2>&1; then
echo "FAIL: Manifest without signature accepted!" echo "FAIL: Manifest without signature accepted!" >&2
exit 1 exit 1
fi fi
echo "PASS: Missing signature rejected." echo "PASS: Missing signature rejected."
echo "Test 4: Signature made by another identity" echo "Test 4: Signature made by another identity"
# Generate a local keypair and sign the manifest
export COSIGN_PASSWORD="" export COSIGN_PASSWORD=""
cosign generate-key-pair cosign generate-key-pair >/dev/null
cosign sign-blob --yes --key cosign.key --output-signature fake.sig server-monitor-manager-manifest.json cosign sign-blob --yes --key cosign.key \
# Verification must fail because ochenstarik-server-monitor-manager.sh enforces keyless OIDC identity! --output-signature fake.sig server-monitor-manager-manifest.json >/dev/null
if ./ochenstarik-server-monitor-manager.sh verify-manifest server-monitor-manager-manifest.json fake.sig >/dev/null 2>&1; then if ./ochenstarik-server-monitor-manager.sh verify-manifest server-monitor-manager-manifest.json \
echo "FAIL: Signature from wrong identity accepted!" fake.sig server-monitor-manager-manifest.pem >/dev/null 2>&1; then
echo "FAIL: Signature from wrong identity accepted!" >&2
exit 1 exit 1
fi fi
echo "PASS: Wrong identity signature rejected." echo "PASS: Wrong identity signature rejected."
rm cosign.key cosign.pub fake.sig rm -f cosign.key cosign.pub fake.sig
echo "Test 5: Missing certificate beside the archive"
mkdir -p no-cert && cp "$ARCHIVE" "$ARCHIVE.sha256" \
server-monitor-manager-manifest.json server-monitor-manager-manifest.sig no-cert/
if ./ochenstarik-server-monitor-manager.sh verify-release "no-cert/$ARCHIVE" >/dev/null 2>&1; then
echo "FAIL: Archive accepted without the signing certificate!" >&2
exit 1
fi
echo "PASS: Missing certificate rejected."
rm -rf no-cert
echo "All negative tests passed!" echo "All negative tests passed!"

View file

@ -1,89 +1,106 @@
#!/bin/bash #!/bin/bash
# Installs a published release exactly the way an operator does it: public curl
# downloads, checksum verification, signature verification, then the documented
# bootstrap commands. No gh CLI and no token, because the operator has neither —
# and because gh resolves the repository from git context, which the isolated
# workspace deliberately removes.
set -euo pipefail set -euo pipefail
TAG="${1:-}" TAG="${1:-}"
REPOSITORY="${SMM_REPOSITORY:-ochenstarik-ui/server-monitor-manager}"
if [[ -z "$TAG" ]]; then if [[ -z "$TAG" ]]; then
echo "Usage: $0 <tag>" echo "Usage: $0 <tag>" >&2
exit 1 exit 1
fi fi
BASE_URL="https://github.com/${REPOSITORY}/releases/download/${TAG}"
CONTROL_PORT=17443
MONITOR_USER="ochenstarik-monitor"
MONITOR_HOME="/var/lib/ochenstarik-monitor"
METRICS_SCRIPT="/usr/local/libexec/ochenstarik-smm-metrics"
echo "Running positive installation test for $TAG..." echo "Running positive installation test for $TAG..."
# Fetch smm-setup.sh download() {
gh release download "$TAG" -p 'smm-setup.sh*' local name="$1"
curl --fail --silent --show-error --location --retry 3 \
-o "$name" "${BASE_URL}/${name}" \
|| { echo "FAIL: asset is not downloadable: $name" >&2; exit 1; }
}
# Verify checksum case "$(uname -m)" in
x86_64) RUNTIME="linux-x64" ;;
aarch64|arm64) RUNTIME="linux-arm64" ;;
*) echo "FAIL: unsupported architecture $(uname -m)" >&2; exit 1 ;;
esac
ARCHIVE="server-monitor-manager-${RUNTIME}.tar.gz"
download smm-setup.sh
download smm-setup.sh.sha256
sha256sum -c smm-setup.sh.sha256 sha256sum -c smm-setup.sh.sha256
# The archive is downloaded by verify-release or we must download it? download "$ARCHIVE"
# In smm-setup.sh, the owner manually downloads the archive? download "$ARCHIVE.sha256"
# Wait, let's look at docs: "загрузка bootstrap и архива из релиза, проверка контрольных сумм, проверка подписи manifest"
# Actually, the user does:
ARCHIVE="server-monitor-manager-linux-$(uname -m | sed -e 's/x86_64/x64/' -e 's/aarch64/arm64/').tar.gz"
gh release download "$TAG" -p "$ARCHIVE*"
gh release download "$TAG" -p "server-monitor-manager-manifest.*"
sha256sum -c "$ARCHIVE.sha256" sha256sum -c "$ARCHIVE.sha256"
# Run setup steps through smm-setup.sh # Signature material must sit beside the archive: verify_archive looks for it there.
# "preflight, verify-release, установка Control, mesh-init" download server-monitor-manager-manifest.json
sudo bash smm-setup.sh preflight download server-monitor-manager-manifest.sig
sudo bash smm-setup.sh verify-manifest server-monitor-manager-manifest.json server-monitor-manager-manifest.sig download server-monitor-manager-manifest.pem
sudo bash smm-setup.sh verify-release "$ARCHIVE"
sudo bash smm-setup.sh install-control "$ARCHIVE" 127.0.0.1 17443 sudo bash smm-setup.sh --tag "$TAG" preflight
sudo bash smm-setup.sh mesh-init 127.0.0.1 51820 sudo bash smm-setup.sh --tag "$TAG" verify-manifest \
server-monitor-manager-manifest.json \
server-monitor-manager-manifest.sig \
server-monitor-manager-manifest.pem
sudo bash smm-setup.sh --tag "$TAG" verify-release "$ARCHIVE"
sudo bash smm-setup.sh --tag "$TAG" install-control "$ARCHIVE" 127.0.0.1 "$CONTROL_PORT"
sudo bash smm-setup.sh --tag "$TAG" mesh-init 127.0.0.1 51820
echo "Checking Control healthz..." echo "Checking Control healthz..."
for _ in {1..30}; do for _ in {1..30}; do
if sudo curl --fail --silent \ if sudo curl --fail --silent \
--cacert /etc/ochenstarik-server-monitor-manager/control-ca.crt \ --cacert /etc/ochenstarik-server-monitor-manager/control-ca.crt \
"https://127.0.0.1:17443/healthz" >/dev/null; then "https://127.0.0.1:${CONTROL_PORT}/healthz" >/dev/null; then
break break
fi fi
sleep 1 sleep 1
done done
sudo curl --fail --silent --show-error \ sudo curl --fail --silent --show-error \
--cacert /etc/ochenstarik-server-monitor-manager/control-ca.crt \ --cacert /etc/ochenstarik-server-monitor-manager/control-ca.crt \
"https://127.0.0.1:17443/healthz" "https://127.0.0.1:${CONTROL_PORT}/healthz"
echo "Extracting node code and installing agent..." echo "Enrolling a node..."
NODE_CODE=$(sudo bash smm-setup.sh node-code test-node) NODE_CODE="$(sudo bash smm-setup.sh --tag "$TAG" node-code test-node)"
export SMM_ENROLL_CODE="$NODE_CODE" SMM_ENROLL_CODE="$NODE_CODE" SMM_ACCEPT_CA_FINGERPRINT=1 \
export SMM_ACCEPT_CA_FINGERPRINT=1 sudo --preserve-env=SMM_ENROLL_CODE,SMM_ACCEPT_CA_FINGERPRINT \
sudo --preserve-env=SMM_ENROLL_CODE,SMM_ACCEPT_CA_FINGERPRINT bash smm-setup.sh install-node "$ARCHIVE" bash smm-setup.sh --tag "$TAG" install-node "$ARCHIVE"
sudo systemctl is-active --quiet ochenstarik-smm-agent.service sudo systemctl is-active --quiet ochenstarik-smm-agent.service
sudo systemctl is-active --quiet ochenstarik-smm-control.service sudo systemctl is-active --quiet ochenstarik-smm-control.service
# Verify install-monitor echo "Installing monitor role..."
echo "Installing monitor..." ssh-keygen -t ed25519 -N "" -f /tmp/monitor_key -q
# Generate a dummy SSH key for the test sudo bash smm-setup.sh --tag "$TAG" install-monitor "$(cat /tmp/monitor_key.pub)"
ssh-keygen -t ed25519 -N "" -f /tmp/monitor_key
MONITOR_PUB=$(cat /tmp/monitor_key.pub)
sudo bash smm-setup.sh install-monitor "$MONITOR_PUB"
echo "Verifying monitor user and forced command..." echo "Verifying monitor snapshot against the contract..."
# Run SSH locally as the monitor user (assuming ssh is configured, but actually we can just su into the user or run the forced command directly) sudo grep -Fq "command=\"${METRICS_SCRIPT}\"" "${MONITOR_HOME}/.ssh/authorized_keys" \
# The forced command is likely defined in ~smm-monitor/.ssh/authorized_keys || { echo "FAIL: forced command is not pinned in authorized_keys" >&2; exit 1; }
MONITOR_CMD=$(sudo cat /var/lib/ochenstarik-server-monitor-manager/monitor/.ssh/authorized_keys | grep -o 'command="[^"]*"' | cut -d'"' -f2)
SNAPSHOT=$(sudo -u ochenstarik-smm-monitor $MONITOR_CMD)
# Simple validation of snapshot fields (since actual values vary, we just check keys) SNAPSHOT="$(sudo -u "$MONITOR_USER" "$METRICS_SCRIPT")"
EXPECTED_KEYS=$(cat tests/contracts/monitor-snapshot-v1.txt | cut -d'=' -f1 | sort) EXPECTED_KEYS="$(cut -d'=' -f1 tests/contracts/monitor-snapshot-v1.txt | sort)"
ACTUAL_KEYS=$(echo "$SNAPSHOT" | cut -d'=' -f1 | sort) ACTUAL_KEYS="$(cut -d'=' -f1 <<<"$SNAPSHOT" | sort)"
if [[ "$EXPECTED_KEYS" == "$ACTUAL_KEYS" ]]; then if [[ "$EXPECTED_KEYS" != "$ACTUAL_KEYS" ]]; then
echo "Monitor snapshot keys match contract." echo "FAIL: monitor snapshot keys do not match the contract" >&2
else diff <(echo "$EXPECTED_KEYS") <(echo "$ACTUAL_KEYS") >&2 || true
echo "Monitor snapshot keys mismatch!"
diff <(echo "$EXPECTED_KEYS") <(echo "$ACTUAL_KEYS") || true
exit 1 exit 1
fi fi
echo "PASS: monitor snapshot matches the contract"
# Verify uninstall sudo bash smm-setup.sh --tag "$TAG" uninstall-monitor
sudo bash smm-setup.sh uninstall-monitor sudo bash smm-setup.sh --tag "$TAG" uninstall-agent --purge
sudo bash smm-setup.sh uninstall-agent --purge sudo bash smm-setup.sh --tag "$TAG" uninstall-control --confirm-destroy-control
sudo bash smm-setup.sh uninstall-control --confirm-destroy-control
echo "Positive installation test passed!" echo "Positive installation test passed!"

View file

@ -30,6 +30,7 @@ smm-setup.sh
smm-setup.sh.sha256 smm-setup.sh.sha256
server-monitor-manager-manifest.json server-monitor-manager-manifest.json
server-monitor-manager-manifest.sig server-monitor-manager-manifest.sig
server-monitor-manager-manifest.pem
EOF EOF
) )