package service import ( "strings" "testing" "hy2xs-admin/dao" "hy2xs-admin/model/dto" ) func strPtr(v string) *string { return &v } // Пир установщика защищён во всех путях записи, а не только в импорте. // // Раньше проверка ReservedBootstrapPeerName стояла исключительно в // applyPeerImportEntry: секрет bootstrap-admin-peer продублирован в // /etc/hy2xs/bootstrap-admin.secret, и смена его через обычную форму панели // молча рассинхронизировала бы файл с базой. func TestUpdatePeerRefusesToResecretBootstrapPeer(t *testing.T) { newTestDB(t) id := seedPeer(t, ReservedBootstrapPeerName, ReservedBootstrapPeerName) before := snapshotPeers(t)[ReservedBootstrapPeerName] err := UpdatePeer(id, dto.PeerUpdateDto{Secret: strPtr("attacker-chosen-secret")}) if err == nil { t.Fatal("смена секрета пира установщика не отклонена") } if !strings.Contains(err.Error(), ReservedBootstrapPeerName) { t.Errorf("отказ не называет пира: %v", err) } after := snapshotPeers(t)[ReservedBootstrapPeerName] if after.SecretDigest == nil || before.SecretDigest == nil || *after.SecretDigest != *before.SecretDigest { t.Fatal("секрет пира установщика изменился несмотря на отказ") } } func TestUpdatePeerRefusesToRenameBootstrapPeer(t *testing.T) { newTestDB(t) id := seedPeer(t, ReservedBootstrapPeerName, ReservedBootstrapPeerName) if err := UpdatePeer(id, dto.PeerUpdateDto{Name: strPtr("renamed-peer")}); err == nil { t.Fatal("переименование пира установщика не отклонено") } if _, ok := snapshotPeers(t)[ReservedBootstrapPeerName]; !ok { t.Fatal("пир установщика переименован несмотря на отказ") } } // Зарезервированное имя нельзя занять чужому пиру. UNIQUE(name) закрывает // этот путь только пока bootstrap-пир существует, а его разрешено удалять. func TestUpdatePeerRefusesToTakeReservedName(t *testing.T) { newTestDB(t) id := seedPeer(t, "ordinary-peer", "ordinary-auth-id") if err := UpdatePeer(id, dto.PeerUpdateDto{Name: strPtr(ReservedBootstrapPeerName)}); err == nil { t.Fatal("переименование обычного пира в зарезервированное имя не отклонено") } peers := snapshotPeers(t) if _, ok := peers["ordinary-peer"]; !ok { t.Fatal("обычный пир переименован несмотря на отказ") } } func TestCreatePeerRefusesReservedName(t *testing.T) { newTestDB(t) name := ReservedBootstrapPeerName quota := int64(-1) if _, err := CreatePeer(dto.PeerSaveDto{Name: &name, QuotaBytes: "a}); err == nil { t.Fatal("создание пира с зарезервированным именем не отклонено") } } // Изменения, не затрагивающие идентичность и секрет, обязаны проходить: иначе // оператор не смог бы ни отключить bootstrap-пира, ни ограничить ему квоту. func TestUpdatePeerAllowsNonIdentityChangesOnBootstrapPeer(t *testing.T) { newTestDB(t) id := seedPeer(t, ReservedBootstrapPeerName, ReservedBootstrapPeerName) disabled := int64(1) if err := UpdatePeer(id, dto.PeerUpdateDto{Disabled: &disabled}); err != nil { t.Fatalf("отключение пира установщика отклонено: %v", err) } peer := snapshotPeers(t)[ReservedBootstrapPeerName] if peer.Disabled == nil || *peer.Disabled != 1 { t.Fatal("отключение не применилось") } } // Удаление разрешено намеренно: bootstrap-пир после установки — действующий // доступ, секрет которого лежит ещё и в файле на диске, и оператор обязан // иметь возможность его отозвать. Расхождения база/файл удаление не создаёт. func TestDeletePeerAllowsRemovingBootstrapPeer(t *testing.T) { newTestDB(t) id := seedPeer(t, ReservedBootstrapPeerName, ReservedBootstrapPeerName) if err := DeletePeer(id); err != nil { t.Fatalf("удаление пира установщика отклонено: %v", err) } if _, err := dao.GetPeer("name = ?", ReservedBootstrapPeerName); err == nil { t.Fatal("пир установщика остался в базе после удаления") } }