Механізм відкату існував з 0035 і жодного разу не виконувався на справжньому обладнанні. Прогін до межі заліза (план -> заявка -> погодження, без заливки) дав три правильні відмови й одну знахідку. D-Link питає пароль інтерактивно, і `show config` віддає відповіді окремими рядками без ключових слів. Порядкове маскування за зразками такий рядок не бачить, тож пароль адміністратора живого комутатора лежав у плані відкату, у ncm.rollbacks і в git-дзеркалі — маскування перед записом у git немає взагалі. redactLines отримав стан: після рядка заведення облікового запису до двох односкладових рядків маскуються. Ім'я запису лишається видимим. Дорогою: перший рядок збереженого конфігу — відлуння команди (`Command: show config`), і планувальникклав його в команди до заливки. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
73 lines
2.8 KiB
Go
73 lines
2.8 KiB
Go
package store
|
||
|
||
import (
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// Точний уривок із конфігу живого комутатора D-Link DGS-1100-06/ME,
|
||
// через який вада й знайшлась: пароль адміністратора лежав відкритим у
|
||
// плані відкату, у базі й у git-дзеркалі.
|
||
//
|
||
// Пароль тут вигаданий; форма — справжня.
|
||
func TestAccountPasswordEchoIsMasked(t *testing.T) {
|
||
in := []string{
|
||
"# User Account",
|
||
"create account admin btadmin ",
|
||
"s3cr3tpass",
|
||
"s3cr3tpass",
|
||
"disable password encryption",
|
||
}
|
||
got := redactLines(nil, in)
|
||
|
||
for i, line := range got {
|
||
if strings.Contains(line, "s3cr3tpass") {
|
||
t.Errorf("рядок %d лишив пароль відкритим: %q", i, line)
|
||
}
|
||
}
|
||
if got[2] != "***" || got[3] != "***" {
|
||
t.Errorf("відлуння пароля не замасковане: %q, %q", got[2], got[3])
|
||
}
|
||
// Рядок про сам обліковий запис має лишитись читабельним: людина при
|
||
// погодженні мусить бачити, ЩО саме змінюється.
|
||
if !strings.Contains(got[1], "btadmin") {
|
||
t.Errorf("з'їдено ім'я облікового запису: %q", got[1])
|
||
}
|
||
}
|
||
|
||
// Маскування не має пожирати справжні рядки конфігу.
|
||
func TestAccountEchoDoesNotEatConfig(t *testing.T) {
|
||
in := []string{
|
||
"create account user guest ",
|
||
"pass1",
|
||
"pass1",
|
||
"enable", // односкладова команда одразу після відлуння
|
||
"disable clipaging", // звичайний рядок
|
||
}
|
||
got := redactLines(nil, in)
|
||
if got[3] != "enable" {
|
||
t.Errorf("з'їдено команду після двох рядків відлуння: %q", got[3])
|
||
}
|
||
if got[4] != "disable clipaging" {
|
||
t.Errorf("з'їдено звичайний рядок: %q", got[4])
|
||
}
|
||
}
|
||
|
||
// Односкладовий рядок САМ ПО СОБІ не секрет: маскується лише той, що
|
||
// стоїть одразу після заведення облікового запису.
|
||
func TestBareTokenAloneIsNotMasked(t *testing.T) {
|
||
got := redactLines(nil, []string{"enable", "end", "exit"})
|
||
for i, line := range got {
|
||
if line == "***" {
|
||
t.Errorf("рядок %d замасковано без причини: був %q", i, []string{"enable", "end", "exit"}[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// Стара поведінка не зламана: секрет в одному рядку з ключовим словом.
|
||
func TestInlineSecretStillMasked(t *testing.T) {
|
||
got := redactLines(nil, []string{"snmp-server community public RO"})
|
||
if strings.Contains(got[0], "public") {
|
||
t.Errorf("community лишилась відкритою: %q", got[0])
|
||
}
|
||
}
|