Транспорт збору конфігу бере доступ хоста, а не профіль моделі
Питання «чому всі профілі ssh» виявило справжню помилку. Список правильний: ssh — розумний типовий вибір. Але це поле ВИРІШУВАЛО транспорт, а профіль описує модель, не конкретну коробку. Хост зі старою прошивкою, де є лише telnet, і з чесно заведеним telnet-доступом усе одно набирався по SSH — і не збирався ніколи. inv.credentials.proto вже ніс потрібну відповідь і доїжджав до зонда невикористаним. Тепер транспорт бере доступ; профільне поле лишилось підказкою й діє, поки доступу немає. Логіка винесена в jobTransport() — щоб її можна було перевірити без бази й без пристрою. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
444447d86b
commit
452345da32
4 changed files with 146 additions and 11 deletions
43
HISTORY.md
43
HISTORY.md
|
|
@ -2832,3 +2832,46 @@ MikroTik RouterOS, Linux), кнопка «За зразок» на кожном
|
|||
пошук за назвою/виробником/командою, «лише власні»,
|
||||
«Cisco IOS» зі зразка заповнив усі шість полів
|
||||
```
|
||||
|
||||
## Транспорт вирішує доступ, а не профіль
|
||||
|
||||
### Питання, яке виявило помилку
|
||||
|
||||
«Чому всі готові профілі — ssh? Хіба не має бути telnet?»
|
||||
|
||||
Сам список правильний: `ssh` у всіх 147 вбудованих — це не недогляд, а
|
||||
розумний типовий вибір. Помилка була в тому, **що саме це поле робило**.
|
||||
|
||||
`buildConfigJob` брав транспорт із профілю моделі. А оскільки профіль
|
||||
описує модель, а не конкретну коробку, виходило так: хост зі старою
|
||||
прошивкою, де є лише telnet, і з чесно заведеним telnet-доступом усе
|
||||
одно набирався по SSH — бо профіль моделі каже «ssh». І не збирався
|
||||
ніколи.
|
||||
|
||||
Доступ при цьому вже ніс потрібне поле: `inv.credentials.proto` — саме
|
||||
воно й відповідає на питання «чим до цієї коробки достукатись». Воно
|
||||
доїжджало до зонда в `Credential.Transport` і не використовувалось для
|
||||
набору.
|
||||
|
||||
### Як тепер
|
||||
|
||||
Транспорт бере доступ. Профільне поле лишилось підказкою «чим це залізо
|
||||
зазвичай беруть» і діє, тільки поки доступу немає. Порт іде за
|
||||
транспортом (22/23), а порт із доступу перекриває типовий — залізо за
|
||||
NAT цілком може слухати SSH на 2222.
|
||||
|
||||
Логіка винесена в `jobTransport()` окремою функцією саме щоб її можна
|
||||
було перевірити без бази й без пристрою: чотири випадки, чотири рядки
|
||||
таблиці.
|
||||
|
||||
У формі профілю поле перейменоване на «Типовий транспорт» із підказкою,
|
||||
що реальний задає доступ хоста. Назва колонки — «Типово».
|
||||
|
||||
### Перевірено
|
||||
|
||||
```
|
||||
доступ telnet + профіль ssh → telnet, порт 23
|
||||
доступ ssh + профіль telnet → ssh, порт 22
|
||||
порт 2222 у доступі → 2222
|
||||
без доступу → лишається підказка профілю
|
||||
```
|
||||
|
|
|
|||
|
|
@ -160,15 +160,7 @@ func (s *Store) buildConfigJob(ctx context.Context, tenantID, deviceID, jobID st
|
|||
return nil, err
|
||||
}
|
||||
|
||||
transport := npv1.Transport_TRANSPORT_SSH
|
||||
port := uint32(22)
|
||||
if prof.Transport == "telnet" {
|
||||
transport = npv1.Transport_TRANSPORT_TELNET
|
||||
port = 23
|
||||
}
|
||||
if cred != nil && cred.Port != 0 {
|
||||
port = cred.Port
|
||||
}
|
||||
transport, port := jobTransport(prof.Transport, cred)
|
||||
|
||||
return &npv1.ConfigJob{
|
||||
JobId: jobID,
|
||||
|
|
@ -394,3 +386,36 @@ func (s *Store) ListConfigJobs(ctx context.Context, tenantID, deviceID string, l
|
|||
})
|
||||
return out, err
|
||||
}
|
||||
|
||||
// jobTransport каже, чим набирати цей хост і на який порт.
|
||||
//
|
||||
// Транспорт вирішує ДОСТУП, а не профіль. Профіль описує модель заліза:
|
||||
// які команди віддати й як упізнати запрошення. А чим до конкретної
|
||||
// коробки достукатись — властивість самої коробки: та сама модель у
|
||||
// клієнта може стояти з увімкненим SSH, а на сусідньому вузлі — зі
|
||||
// старою прошивкою, де є лише telnet.
|
||||
//
|
||||
// Раніше вирішував профіль, і оскільки всі 147 вбудованих кажуть «ssh»,
|
||||
// хост із telnet-доступом набирався по SSH і не збирався ніколи.
|
||||
// Профільне поле лишилось підказкою «чим це залізо зазвичай беруть» і
|
||||
// діє, тільки поки доступу немає.
|
||||
func jobTransport(profileTransport string, cred *npv1.Credential) (npv1.Transport, uint32) {
|
||||
transport := npv1.Transport_TRANSPORT_SSH
|
||||
switch {
|
||||
case cred != nil:
|
||||
transport = cred.Transport
|
||||
case profileTransport == "telnet":
|
||||
transport = npv1.Transport_TRANSPORT_TELNET
|
||||
}
|
||||
|
||||
port := uint32(22)
|
||||
if transport == npv1.Transport_TRANSPORT_TELNET {
|
||||
port = 23
|
||||
}
|
||||
// Порт із доступу перекриває типовий: залізо за NAT цілком може
|
||||
// слухати SSH на 2222.
|
||||
if cred != nil && cred.Port != 0 {
|
||||
port = cred.Port
|
||||
}
|
||||
return transport, port
|
||||
}
|
||||
|
|
|
|||
64
server/internal/store/ncm_transport_test.go
Normal file
64
server/internal/store/ncm_transport_test.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
npv1 "github.com/netpulse/netpulse/gen/go/netpulse/v1"
|
||||
)
|
||||
|
||||
// Транспорт задає доступ хоста, а не профіль моделі.
|
||||
//
|
||||
// Усі 147 вбудованих профілів кажуть «ssh», бо так знімають з
|
||||
// переважної більшості заліза. Але та сама модель у клієнта може стояти
|
||||
// зі старою прошивкою, де є лише telnet, — і поки вирішував профіль,
|
||||
// такий хост набирався по SSH і не збирався ніколи.
|
||||
func TestTransportComesFromCredential(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
profile string
|
||||
cred *npv1.Credential
|
||||
want npv1.Transport
|
||||
port uint32
|
||||
}{
|
||||
{
|
||||
name: "доступ telnet перемагає профіль ssh",
|
||||
profile: "ssh",
|
||||
cred: &npv1.Credential{Transport: npv1.Transport_TRANSPORT_TELNET},
|
||||
want: npv1.Transport_TRANSPORT_TELNET,
|
||||
port: 23,
|
||||
},
|
||||
{
|
||||
name: "доступ ssh перемагає профіль telnet",
|
||||
profile: "telnet",
|
||||
cred: &npv1.Credential{Transport: npv1.Transport_TRANSPORT_SSH},
|
||||
want: npv1.Transport_TRANSPORT_SSH,
|
||||
port: 22,
|
||||
},
|
||||
{
|
||||
name: "порт з доступу перекриває типовий",
|
||||
profile: "ssh",
|
||||
cred: &npv1.Credential{Transport: npv1.Transport_TRANSPORT_SSH, Port: 2222},
|
||||
want: npv1.Transport_TRANSPORT_SSH,
|
||||
port: 2222,
|
||||
},
|
||||
{
|
||||
name: "без доступу лишається підказка профілю",
|
||||
profile: "telnet",
|
||||
cred: nil,
|
||||
want: npv1.Transport_TRANSPORT_TELNET,
|
||||
port: 23,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
got, port := jobTransport(c.profile, c.cred)
|
||||
if got != c.want {
|
||||
t.Fatalf("транспорт: %v, очікував %v", got, c.want)
|
||||
}
|
||||
if port != c.port {
|
||||
t.Fatalf("порт: %d, очікував %d", port, c.port)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -208,7 +208,7 @@ export function ProfilesPage() {
|
|||
},
|
||||
{
|
||||
key: 'transport',
|
||||
header: 'Транспорт',
|
||||
header: 'Типово',
|
||||
cell: (p) => <span className="text-slate-400">{p.transport}</span>,
|
||||
},
|
||||
{
|
||||
|
|
@ -413,7 +413,10 @@ function ProfileForm({
|
|||
onChange={(e) => setVendor(e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Транспорт">
|
||||
<Field
|
||||
label="Типовий транспорт"
|
||||
hint="Підказка, чим це залізо зазвичай беруть. Реальний транспорт задає доступ, прив'язаний до хоста"
|
||||
>
|
||||
<select
|
||||
className={inputClass}
|
||||
value={transport}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue