diff --git a/internal/convertor/device/device_test.go b/internal/convertor/device/device_test.go index 1cb59d2..24b75c5 100644 --- a/internal/convertor/device/device_test.go +++ b/internal/convertor/device/device_test.go @@ -28,7 +28,7 @@ func TestGenerateconfigsSystem(t *testing.T) { { name: "NTP and TACACS+", ntp: &ntp.NTP{ServerList: []ntp.Server{{Name: "ntp1", ServerAddress: "10.0.0.1"}}}, - tacacs: &tacacs.Tacacs{Passkey: "secret", ServerList: []tacacs.Server{{ServerAddress: "10.10.10.10", Priority: 1, TCPPort: 49}}}, + tacacs: &tacacs.Tacacs{ServerList: []tacacs.Server{{ServerAddress: "10.10.10.10", Priority: 1, TCPPort: 49, Passkey: "secret"}}}, wantKeys: []string{"aaa", "config", "ntp"}, }, { diff --git a/internal/convertor/tacacs/tacacs.go b/internal/convertor/tacacs/tacacs.go index 44a3b9c..af8dbe4 100644 --- a/internal/convertor/tacacs/tacacs.go +++ b/internal/convertor/tacacs/tacacs.go @@ -17,17 +17,12 @@ const tacacsServerGroupName = "TACACS+" // TacacsToOpenConfigAAA converts a CMDB TACACS configuration into the aaa // container of openconfig-system, under a single TACACS+ server group. // -// It returns nil when there is no server to advertise: the CMDB holds a -// single passkey for the whole device, and the model has no place for it -// outside of a server entry. +// It returns nil when there is no server to advertise. // // OpenConfig's server list has no ordering or priority leaf of its own, so // the CMDB priority is carried by the criteo-aaa-ext priority leaf instead of // the position in the list. Servers are still appended by descending // priority, ties broken on the address, to keep the emitted JSON stable. -// -// The CMDB holds one passkey per device where the model has one shared -// secret per server, so it is set on each of them. func TacacsToOpenConfigAAA(config *tacacs.Tacacs) (*openconfig.System_Aaa, error) { if config == nil || len(config.ServerList) == 0 { return nil, nil @@ -59,13 +54,13 @@ func TacacsToOpenConfigAAA(config *tacacs.Tacacs) (*openconfig.System_Aaa, error entry.Priority = ygot.Uint32(server.Priority) } - if server.TCPPort != 0 || config.Passkey != "" { + if server.TCPPort != 0 || server.Passkey != "" { entry.Tacacs = &openconfig.System_Aaa_ServerGroup_Server_Tacacs{} if server.TCPPort != 0 { entry.Tacacs.Port = ygot.Uint16(server.TCPPort) } - if config.Passkey != "" { - entry.Tacacs.SecretKey = ygot.String(config.Passkey) + if server.Passkey != "" { + entry.Tacacs.SecretKey = ygot.String(server.Passkey) } } } diff --git a/internal/convertor/tacacs/tacacs_test.go b/internal/convertor/tacacs/tacacs_test.go index 0efb8aa..41a6a4a 100644 --- a/internal/convertor/tacacs/tacacs_test.go +++ b/internal/convertor/tacacs/tacacs_test.go @@ -43,9 +43,8 @@ func TestTacacsToOpenConfigAAA(t *testing.T) { { name: "one server", args: &tacacs.Tacacs{ - Passkey: "Mypasword1", ServerList: []tacacs.Server{ - {ServerAddress: "1.1.1.1", Priority: 1, TCPPort: 49}, + {ServerAddress: "1.1.1.1", Priority: 1, TCPPort: 49, Passkey: "Mypasword1"}, }, }, want: map[string]uint32{"1.1.1.1": 1}, @@ -53,11 +52,10 @@ func TestTacacsToOpenConfigAAA(t *testing.T) { { name: "several servers, each keeps its own priority", args: &tacacs.Tacacs{ - Passkey: "Mypasword1", ServerList: []tacacs.Server{ - {ServerAddress: "1.1.1.3", Priority: 1, TCPPort: 49}, - {ServerAddress: "1.1.1.1", Priority: 3, TCPPort: 49}, - {ServerAddress: "1.1.1.2", Priority: 2, TCPPort: 4949}, + {ServerAddress: "1.1.1.3", Priority: 1, TCPPort: 49, Passkey: "Mypasword1"}, + {ServerAddress: "1.1.1.1", Priority: 3, TCPPort: 49, Passkey: "Mypasword1"}, + {ServerAddress: "1.1.1.2", Priority: 2, TCPPort: 4949, Passkey: "Mypasword1"}, }, }, want: map[string]uint32{"1.1.1.3": 1, "1.1.1.1": 3, "1.1.1.2": 2}, @@ -91,14 +89,13 @@ func TestTacacsToOpenConfigAAA(t *testing.T) { func TestTacacsToOpenConfigAAAJSON(t *testing.T) { want := `{"system":{"aaa":{"server-groups":{"server-group":[{"config":{"name":"TACACS+","type":"TACACS"},"name":"TACACS+","servers":{"server":[` + `{"address":"1.1.1.1","config":{"address":"1.1.1.1","priority":2},"tacacs":{"config":{"port":49,"secret-key":"Mypasword1"}}},` + - `{"address":"1.1.1.2","config":{"address":"1.1.1.2","priority":1},"tacacs":{"config":{"port":4949,"secret-key":"Mypasword1"}}}` + + `{"address":"1.1.1.2","config":{"address":"1.1.1.2","priority":1},"tacacs":{"config":{"port":4949,"secret-key":"Mypasword2"}}}` + `]}}]}}}}` config := &tacacs.Tacacs{ - Passkey: "Mypasword1", ServerList: []tacacs.Server{ - {ServerAddress: "1.1.1.2", Priority: 1, TCPPort: 4949}, - {ServerAddress: "1.1.1.1", Priority: 2, TCPPort: 49}, + {ServerAddress: "1.1.1.2", Priority: 1, TCPPort: 4949, Passkey: "Mypasword2"}, + {ServerAddress: "1.1.1.1", Priority: 2, TCPPort: 49, Passkey: "Mypasword1"}, }, } @@ -131,13 +128,11 @@ func TestTacacsToOpenConfigAAADefaults(t *testing.T) { } } -// TestTacacsToOpenConfigAAANoServer checks nothing is generated without a server: -// the passkey alone has no place in the model. +// TestTacacsToOpenConfigAAANoServer checks nothing is generated without a server. func TestTacacsToOpenConfigAAANoServer(t *testing.T) { tests := map[string]*tacacs.Tacacs{ "no configuration at all": nil, "empty configuration": {}, - "passkey but no server": {Passkey: "Mypasword1"}, } for name, config := range tests { diff --git a/internal/ingestor/cmdb/tacacs_test.go b/internal/ingestor/cmdb/tacacs_test.go index 341bf7b..be8f187 100644 --- a/internal/ingestor/cmdb/tacacs_test.go +++ b/internal/ingestor/cmdb/tacacs_test.go @@ -24,19 +24,20 @@ func TestPrecomputeTacacs(t *testing.T) { "id":1, "name":"tor01-01" }, - "passkey":"Mypasword1", "server_list":[ { "id":1, "server_address":"1.1.1.1", "priority":1, - "tcp_port":49 + "tcp_port":49, + "passkey":"Mypasword1" }, { "id":2, "server_address":"1.1.1.2", "priority":2, - "tcp_port":4949 + "tcp_port":4949, + "passkey":"Mypasword2" } ], "created":"2026-08-10T07:37:29.238195Z", @@ -50,17 +51,18 @@ func TestPrecomputeTacacs(t *testing.T) { }{ Name: "tor01-01", }, - Passkey: "Mypasword1", ServerList: []tacacs.Server{ { ServerAddress: "1.1.1.1", Priority: 1, TCPPort: 49, + Passkey: "Mypasword1", }, { ServerAddress: "1.1.1.2", Priority: 2, TCPPort: 4949, + Passkey: "Mypasword2", }, }, }, diff --git a/internal/model/cmdb/tacacs/global.go b/internal/model/cmdb/tacacs/global.go index 552ab84..62ad03b 100644 --- a/internal/model/cmdb/tacacs/global.go +++ b/internal/model/cmdb/tacacs/global.go @@ -8,13 +8,13 @@ type Server struct { ServerAddress string `json:"server_address" validate:"required,ip"` Priority uint32 `json:"priority" validate:"omitempty"` TCPPort uint16 `json:"tcp_port" validate:"omitempty"` + Passkey string `json:"passkey" validate:"omitempty"` } -// Tacacs is the TACACS configuration of a single device: its global settings and its servers. +// Tacacs is the TACACS configuration of a single device: its servers. type Tacacs struct { Device struct { Name string `json:"name" validate:"required"` } `json:"device" validate:"required"` - Passkey string `json:"passkey" validate:"omitempty"` ServerList []Server `json:"server_list" validate:"omitempty,dive"` }