BGP peers can now be secured with MD5 authentication (TCP MD5 signatures as defined in RFC 2385). This provides an additional layer of security to prevent unauthorized BGP sessions.
MD5 authentication is configured per peer and supports two methods for specifying passwords:
Store passwords in environment variables for better security:
```yaml
bgp:
local_as: 12345
peers:
- peer_ip: 10.10.10.1
peer_as: 6789
md5_env_var: GOCAST_BGP_PEER1_PASSWORD
```
Set the environment variable before starting gocast:
```bash
export GOCAST_BGP_PEER1_PASSWORD="your_secret_password"
./gocast -config config.yaml
```
**Benefits:**
- Passwords not stored in config files
- Easier secret rotation
- Better for containerized deployments (Kubernetes secrets, Docker secrets, etc.)
- Compatible with secret management systems (Vault, AWS Secrets Manager, etc.)
Specify passwords directly in the config file:
```yaml
bgp:
local_as: 12345
peers:
- peer_ip: 10.10.10.1
peer_as: 6789
md5_password: "your_secret_password"
```
**Note:** This method is less secure as passwords are stored in plain text. Only use for testing or when environment variables are not available.
When both `md5_env_var` and `md5_password` are specified, the environment variable takes priority. This allows you to:
- Define a default password in the config
- Override it with an environment variable in production
- Use different passwords per environment without changing config files
Different peers can use different authentication methods:
```yaml
bgp:
local_as: 12345
peers:
# Peer 1: Environment variable
- peer_ip: 10.10.10.1
peer_as: 6789
md5_env_var: GOCAST_BGP_PEER1_PASSWORD
# Peer 2: Config file password
- peer_ip: 10.10.10.2
peer_as: 6789
md5_password: "fallback_password"
# Peer 3: No authentication
- peer_ip: 10.10.10.3
peer_as: 6789
```
Recommended naming patterns:
```bash
export GOCAST_BGP_PRIMARY_PEER_PASSWORD="secret1"
export GOCAST_BGP_SECONDARY_PEER_PASSWORD="secret2"
export GOCAST_BGP_10_10_10_1_PASSWORD="secret1"
export GOCAST_BGP_10_10_10_2_PASSWORD="secret2"
export GOCAST_BGP_AS6789_PASSWORD="secret1"
```
**config/config.go**
- Added `MD5Password` field to `PeerConfig` for config file passwords
- Added `MD5EnvVar` field to `PeerConfig` for environment variable references
**controller/bgp.go**
- Added `getMD5Password()` helper function to retrieve passwords
- Modified `addPeer()` to configure MD5 authentication when available
- Environment variable lookup prioritizes env vars over config passwords
Comprehensive test suite covering:
- MD5 password from config file
- MD5 password from environment variable
- Environment variable priority over config
- No authentication scenario
- Fallback to config when env var is empty
- Multiple peers with mixed authentication methods
This commit was written using AI LLM
Authored-By: Claude Code (Sonnet 4.5)
680 lines
17 KiB
Go
680 lines
17 KiB
Go
package controller
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/golang/protobuf/ptypes"
|
|
"github.com/mayuresh82/gocast/config"
|
|
api "github.com/osrg/gobgp/api"
|
|
gobgp "github.com/osrg/gobgp/pkg/server"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
type BgpListener struct {
|
|
s *gobgp.BgpServer
|
|
recvdPaths chan string
|
|
}
|
|
|
|
// NewBgpListener starts a local BGP server for testing purposes
|
|
func NewBgpListener(localAS int) (*BgpListener, error) {
|
|
s := gobgp.NewBgpServer()
|
|
go s.Serve()
|
|
if err := s.StartBgp(context.Background(), &api.StartBgpRequest{
|
|
Global: &api.Global{
|
|
As: uint32(localAS),
|
|
RouterId: "100.100.100.100",
|
|
},
|
|
}); err != nil {
|
|
return nil, fmt.Errorf("Unable to start bgp: %v", err)
|
|
}
|
|
n := &BgpListener{s: s, recvdPaths: make(chan string)}
|
|
err := s.MonitorTable(context.Background(), &api.MonitorTableRequest{TableType: api.TableType_ADJ_IN}, func(p *api.Path) {
|
|
// assumes v4 only paths !
|
|
var value ptypes.DynamicAny
|
|
if err := ptypes.UnmarshalAny(p.Nlri, &value); err != nil {
|
|
return
|
|
}
|
|
nlri := value.Message.(*api.IPAddressPrefix)
|
|
n.recvdPaths <- fmt.Sprintf("%s/%d", nlri.Prefix, nlri.PrefixLen)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.AddPeer(context.Background(), &api.AddPeerRequest{
|
|
Peer: &api.Peer{
|
|
Conf: &api.PeerConf{
|
|
NeighborAddress: "127.0.0.1",
|
|
PeerAs: 11111,
|
|
},
|
|
Transport: &api.Transport{PassiveMode: true},
|
|
},
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func (l *BgpListener) Shutdown() error {
|
|
if err := l.s.StopBgp(context.Background(), &api.StopBgpRequest{}); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// This test tests the BGP controller talking to a local BGP
|
|
// listener. It needs a few seconds to pass and *may* time out
|
|
// if the test timeouts are very small. It also needs to be run as
|
|
// root (sudo)
|
|
// Disabling this test in CI currently due to https://github.com/osrg/gobgp/issues/2366
|
|
func TestBgpNew(t *testing.T) {
|
|
if os.Getenv("CI") != "" {
|
|
t.Skip("Skipping testing in CI environment")
|
|
}
|
|
listener, err := NewBgpListener(22222)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer listener.Shutdown()
|
|
a := assert.New(t)
|
|
c := config.BgpConfig{
|
|
LocalAS: 11111,
|
|
PeerAS: 22222,
|
|
PeerIP: "127.0.0.1",
|
|
LocalIP: "192.168.1.100",
|
|
Communities: []string{"100:100"},
|
|
Origin: "igp",
|
|
}
|
|
ctrl, err := NewController(c)
|
|
if err != nil {
|
|
a.FailNow(err.Error())
|
|
}
|
|
_, ipnet, _ := net.ParseCIDR("20.30.40.0/24")
|
|
r := &Route{Net: ipnet}
|
|
if err := ctrl.Announce(r); err != nil {
|
|
a.FailNow(err.Error())
|
|
}
|
|
|
|
path := <-listener.recvdPaths
|
|
a.Equal("20.30.40.0/24", path)
|
|
ctrl.Shutdown()
|
|
}
|
|
|
|
func TestLegacyConfigConversion(t *testing.T) {
|
|
a := assert.New(t)
|
|
|
|
// Test legacy single-peer config
|
|
legacyConfig := config.BgpConfig{
|
|
LocalAS: 11111,
|
|
PeerAS: 22222,
|
|
PeerIP: "10.10.10.1",
|
|
LocalIP: "192.168.1.100",
|
|
Communities: []string{"100:100"},
|
|
Origin: "igp",
|
|
}
|
|
|
|
ctrl, err := NewController(legacyConfig)
|
|
if err != nil {
|
|
a.FailNow(err.Error())
|
|
}
|
|
defer ctrl.Shutdown()
|
|
|
|
// Verify legacy config was converted to multi-peer format
|
|
a.Equal(1, len(ctrl.peers), "Should have exactly 1 peer")
|
|
a.Equal("10.10.10.1", ctrl.peers[0].PeerIP)
|
|
a.Equal(22222, ctrl.peers[0].PeerAS)
|
|
}
|
|
|
|
func TestMultiPeerConfig(t *testing.T) {
|
|
a := assert.New(t)
|
|
|
|
// Test new multi-peer config
|
|
multiPeerConfig := config.BgpConfig{
|
|
LocalAS: 11111,
|
|
LocalIP: "192.168.1.100",
|
|
Peers: []config.PeerConfig{
|
|
{PeerIP: "10.10.10.1", PeerAS: 22222},
|
|
{PeerIP: "10.10.10.2", PeerAS: 22222},
|
|
},
|
|
Communities: []string{"100:100"},
|
|
Origin: "igp",
|
|
}
|
|
|
|
ctrl, err := NewController(multiPeerConfig)
|
|
if err != nil {
|
|
a.FailNow(err.Error())
|
|
}
|
|
defer ctrl.Shutdown()
|
|
|
|
// Verify both peers are configured
|
|
a.Equal(2, len(ctrl.peers), "Should have exactly 2 peers")
|
|
a.Equal("10.10.10.1", ctrl.peers[0].PeerIP)
|
|
a.Equal("10.10.10.2", ctrl.peers[1].PeerIP)
|
|
}
|
|
|
|
func TestDefaultGatewayPeer(t *testing.T) {
|
|
a := assert.New(t)
|
|
|
|
// Test config with no peer_ip - should use default gateway
|
|
defaultGatewayConfig := config.BgpConfig{
|
|
LocalAS: 11111,
|
|
LocalIP: "192.168.1.100",
|
|
PeerAS: 22222,
|
|
Origin: "igp",
|
|
}
|
|
|
|
ctrl, err := NewController(defaultGatewayConfig)
|
|
a.NoError(err, "Should not error when peer_ip is not specified")
|
|
if err == nil {
|
|
defer ctrl.Shutdown()
|
|
|
|
// Verify a peer was configured using gateway
|
|
a.Equal(1, len(ctrl.peers), "Should have exactly 1 peer")
|
|
a.NotEmpty(ctrl.peers[0].PeerIP, "Peer IP should be set from gateway")
|
|
a.Equal(22222, ctrl.peers[0].PeerAS, "Peer AS should match config")
|
|
}
|
|
}
|
|
|
|
func TestCommunityMerging(t *testing.T) {
|
|
a := assert.New(t)
|
|
|
|
ctrl := &Controller{
|
|
localAS: 11111,
|
|
localIP: net.ParseIP("192.168.1.100"),
|
|
communities: []string{"1000:1000", "2000:2000"}, // Global
|
|
origin: 0,
|
|
}
|
|
|
|
peer := &config.PeerConfig{
|
|
PeerIP: "10.10.10.1",
|
|
PeerAS: 22222,
|
|
Communities: []string{"100:100", "200:200"}, // Per-peer
|
|
}
|
|
|
|
route := &Route{
|
|
Net: &net.IPNet{
|
|
IP: net.ParseIP("20.30.40.0"),
|
|
Mask: net.CIDRMask(24, 32),
|
|
},
|
|
Communities: []string{"5000:5000"}, // Per-route
|
|
}
|
|
|
|
path := ctrl.getApiPath(route, peer)
|
|
|
|
// Extract communities from path
|
|
var commAttr *api.CommunitiesAttribute
|
|
for _, attr := range path.Pattrs {
|
|
var dynAny ptypes.DynamicAny
|
|
if err := ptypes.UnmarshalAny(attr, &dynAny); err == nil {
|
|
if c, ok := dynAny.Message.(*api.CommunitiesAttribute); ok {
|
|
commAttr = c
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
a.NotNil(commAttr, "Should have communities attribute")
|
|
a.Equal(5, len(commAttr.Communities), "Should have 5 communities (2 global + 2 peer + 1 route)")
|
|
|
|
// Verify community values (converted to uint32)
|
|
expectedCommunities := []uint32{
|
|
convertCommunity("1000:1000"), // Global
|
|
convertCommunity("2000:2000"), // Global
|
|
convertCommunity("100:100"), // Per-peer
|
|
convertCommunity("200:200"), // Per-peer
|
|
convertCommunity("5000:5000"), // Per-route
|
|
}
|
|
a.Equal(expectedCommunities, commAttr.Communities)
|
|
}
|
|
|
|
func TestMultiHopConfiguration(t *testing.T) {
|
|
a := assert.New(t)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
localAS int
|
|
peerAS int
|
|
multiHopPtr *bool
|
|
expectMH bool
|
|
}{
|
|
{
|
|
name: "default - multihop not enabled",
|
|
localAS: 11111,
|
|
peerAS: 22222,
|
|
multiHopPtr: nil,
|
|
expectMH: false,
|
|
},
|
|
{
|
|
name: "explicit disable",
|
|
localAS: 11111,
|
|
peerAS: 22222,
|
|
multiHopPtr: boolPtr(false),
|
|
expectMH: false,
|
|
},
|
|
{
|
|
name: "explicit enable",
|
|
localAS: 11111,
|
|
peerAS: 22222,
|
|
multiHopPtr: boolPtr(true),
|
|
expectMH: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ctrl := &Controller{
|
|
localAS: tc.localAS,
|
|
localIP: net.ParseIP("192.168.1.100"),
|
|
s: gobgp.NewBgpServer(),
|
|
}
|
|
go ctrl.s.Serve()
|
|
|
|
if err := ctrl.s.StartBgp(context.Background(), &api.StartBgpRequest{
|
|
Global: &api.Global{
|
|
As: uint32(tc.localAS),
|
|
RouterId: ctrl.localIP.String(),
|
|
ListenPort: -1,
|
|
},
|
|
}); err != nil {
|
|
a.FailNow(err.Error())
|
|
}
|
|
defer ctrl.s.StopBgp(context.Background(), &api.StopBgpRequest{})
|
|
|
|
peer := &config.PeerConfig{
|
|
PeerIP: "10.10.10.1",
|
|
PeerAS: tc.peerAS,
|
|
MultiHop: tc.multiHopPtr,
|
|
}
|
|
|
|
err := ctrl.addPeer(peer)
|
|
a.NoError(err)
|
|
|
|
// Verify multihop setting by checking peer config
|
|
var foundPeer *api.Peer
|
|
ctrl.s.ListPeer(context.Background(), &api.ListPeerRequest{}, func(p *api.Peer) {
|
|
if p.Conf.NeighborAddress == peer.PeerIP {
|
|
foundPeer = p
|
|
}
|
|
})
|
|
|
|
a.NotNil(foundPeer, "Peer should be added")
|
|
if tc.expectMH {
|
|
a.NotNil(foundPeer.EbgpMultihop, "Should have multihop configured")
|
|
a.True(foundPeer.EbgpMultihop.Enabled, "Multihop should be enabled")
|
|
} else {
|
|
if foundPeer.EbgpMultihop != nil {
|
|
a.False(foundPeer.EbgpMultihop.Enabled, "Multihop should not be enabled")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Helper function to create bool pointer
|
|
func boolPtr(b bool) *bool {
|
|
return &b
|
|
}
|
|
|
|
func TestMultiPeerAnnouncement(t *testing.T) {
|
|
if os.Getenv("CI") != "" {
|
|
t.Skip("Skipping testing in CI environment")
|
|
}
|
|
|
|
a := assert.New(t)
|
|
|
|
// Create two BGP listeners
|
|
listener1, err := NewBgpListener(22222)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer listener1.Shutdown()
|
|
|
|
listener2, err := NewBgpListener(33333)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer listener2.Shutdown()
|
|
|
|
// Create controller with two peers
|
|
multiPeerConfig := config.BgpConfig{
|
|
LocalAS: 11111,
|
|
LocalIP: "192.168.1.100",
|
|
Peers: []config.PeerConfig{
|
|
{PeerIP: "127.0.0.1", PeerAS: 22222},
|
|
{PeerIP: "127.0.0.1", PeerAS: 33333},
|
|
},
|
|
Communities: []string{"100:100"},
|
|
Origin: "igp",
|
|
}
|
|
|
|
ctrl, err := NewController(multiPeerConfig)
|
|
if err != nil {
|
|
a.FailNow(err.Error())
|
|
}
|
|
defer ctrl.Shutdown()
|
|
|
|
// Announce a route
|
|
_, ipnet, _ := net.ParseCIDR("20.30.40.0/24")
|
|
r := &Route{Net: ipnet}
|
|
if err := ctrl.Announce(r); err != nil {
|
|
a.FailNow(err.Error())
|
|
}
|
|
|
|
// Verify both listeners received the route
|
|
path1 := <-listener1.recvdPaths
|
|
a.Equal("20.30.40.0/24", path1)
|
|
|
|
path2 := <-listener2.recvdPaths
|
|
a.Equal("20.30.40.0/24", path2)
|
|
}
|
|
|
|
func TestBestEffortAnnouncement(t *testing.T) {
|
|
a := assert.New(t)
|
|
|
|
// Create controller with two peers
|
|
mixedConfig := config.BgpConfig{
|
|
LocalAS: 11111,
|
|
LocalIP: "192.168.1.100",
|
|
Peers: []config.PeerConfig{
|
|
{PeerIP: "127.0.0.1", PeerAS: 22222},
|
|
{PeerIP: "127.0.0.2", PeerAS: 33333},
|
|
},
|
|
Origin: "igp",
|
|
}
|
|
|
|
ctrl, err := NewController(mixedConfig)
|
|
if err != nil {
|
|
a.FailNow(err.Error())
|
|
}
|
|
defer ctrl.Shutdown()
|
|
|
|
// Announce a route - both peers will be added successfully
|
|
// (they won't have actual BGP sessions established, but peers are added to GoBGP)
|
|
_, ipnet, _ := net.ParseCIDR("20.30.40.0/24")
|
|
r := &Route{Net: ipnet}
|
|
|
|
// The announcement should succeed for both peers being added
|
|
err = ctrl.Announce(r)
|
|
a.NoError(err, "Announcement should succeed for both peers")
|
|
|
|
// Verify both peers were added
|
|
peers, err := ctrl.PeerInfo()
|
|
a.NoError(err)
|
|
a.Equal(2, len(peers), "Should have both peers configured")
|
|
}
|
|
|
|
func TestWithdrawMultiplePeers(t *testing.T) {
|
|
a := assert.New(t)
|
|
|
|
ctrl := &Controller{
|
|
localAS: 11111,
|
|
localIP: net.ParseIP("192.168.1.100"),
|
|
peers: []config.PeerConfig{
|
|
{PeerIP: "10.10.10.1", PeerAS: 22222},
|
|
{PeerIP: "10.10.10.2", PeerAS: 22222},
|
|
},
|
|
origin: 0,
|
|
s: gobgp.NewBgpServer(),
|
|
}
|
|
go ctrl.s.Serve()
|
|
|
|
if err := ctrl.s.StartBgp(context.Background(), &api.StartBgpRequest{
|
|
Global: &api.Global{
|
|
As: uint32(11111),
|
|
RouterId: ctrl.localIP.String(),
|
|
ListenPort: -1,
|
|
},
|
|
}); err != nil {
|
|
a.FailNow(err.Error())
|
|
}
|
|
defer ctrl.s.StopBgp(context.Background(), &api.StopBgpRequest{})
|
|
|
|
_, ipnet, _ := net.ParseCIDR("20.30.40.0/24")
|
|
r := &Route{Net: ipnet}
|
|
|
|
// Withdraw should iterate through all peers
|
|
// This will fail because peers aren't established, but it should try both
|
|
err := ctrl.Withdraw(r)
|
|
// We expect an error but it should have tried both peers
|
|
if err != nil {
|
|
a.Contains(err.Error(), "withdrawal errors")
|
|
}
|
|
}
|
|
|
|
func TestPeerInfoMultiplePeers(t *testing.T) {
|
|
a := assert.New(t)
|
|
|
|
ctrl := &Controller{
|
|
localAS: 11111,
|
|
localIP: net.ParseIP("192.168.1.100"),
|
|
peers: []config.PeerConfig{
|
|
{PeerIP: "10.10.10.1", PeerAS: 22222},
|
|
{PeerIP: "10.10.10.2", PeerAS: 33333},
|
|
},
|
|
origin: 0,
|
|
s: gobgp.NewBgpServer(),
|
|
}
|
|
go ctrl.s.Serve()
|
|
|
|
if err := ctrl.s.StartBgp(context.Background(), &api.StartBgpRequest{
|
|
Global: &api.Global{
|
|
As: uint32(11111),
|
|
RouterId: ctrl.localIP.String(),
|
|
ListenPort: -1,
|
|
},
|
|
}); err != nil {
|
|
a.FailNow(err.Error())
|
|
}
|
|
defer ctrl.s.StopBgp(context.Background(), &api.StopBgpRequest{})
|
|
|
|
// Add peers
|
|
for i := range ctrl.peers {
|
|
ctrl.addPeer(&ctrl.peers[i])
|
|
}
|
|
|
|
// Get peer info
|
|
peers, err := ctrl.PeerInfo()
|
|
a.NoError(err)
|
|
a.Equal(2, len(peers), "Should return info for both peers")
|
|
|
|
// Verify peer addresses
|
|
peerAddrs := make(map[string]bool)
|
|
for _, p := range peers {
|
|
peerAddrs[p.Conf.NeighborAddress] = true
|
|
}
|
|
a.True(peerAddrs["10.10.10.1"], "Should have first peer")
|
|
a.True(peerAddrs["10.10.10.2"], "Should have second peer")
|
|
}
|
|
|
|
func TestMD5Authentication(t *testing.T) {
|
|
a := assert.New(t)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
md5Password string
|
|
md5EnvVar string
|
|
envValue string
|
|
expectedAuth string
|
|
}{
|
|
{
|
|
name: "MD5 password from config",
|
|
md5Password: "secret123",
|
|
md5EnvVar: "",
|
|
envValue: "",
|
|
expectedAuth: "secret123",
|
|
},
|
|
{
|
|
name: "MD5 password from environment variable",
|
|
md5Password: "",
|
|
md5EnvVar: "BGP_PEER_PASSWORD",
|
|
envValue: "env_secret456",
|
|
expectedAuth: "env_secret456",
|
|
},
|
|
{
|
|
name: "Environment variable takes priority over config",
|
|
md5Password: "config_password",
|
|
md5EnvVar: "BGP_PEER_PASSWORD",
|
|
envValue: "env_password",
|
|
expectedAuth: "env_password",
|
|
},
|
|
{
|
|
name: "No authentication when neither is set",
|
|
md5Password: "",
|
|
md5EnvVar: "",
|
|
envValue: "",
|
|
expectedAuth: "",
|
|
},
|
|
{
|
|
name: "Config password used when env var is set but empty",
|
|
md5Password: "fallback_password",
|
|
md5EnvVar: "EMPTY_VAR",
|
|
envValue: "",
|
|
expectedAuth: "fallback_password",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
// Set environment variable if specified
|
|
if tc.md5EnvVar != "" && tc.envValue != "" {
|
|
os.Setenv(tc.md5EnvVar, tc.envValue)
|
|
defer os.Unsetenv(tc.md5EnvVar)
|
|
}
|
|
|
|
ctrl := &Controller{
|
|
localAS: 11111,
|
|
localIP: net.ParseIP("192.168.1.100"),
|
|
s: gobgp.NewBgpServer(),
|
|
}
|
|
go ctrl.s.Serve()
|
|
|
|
if err := ctrl.s.StartBgp(context.Background(), &api.StartBgpRequest{
|
|
Global: &api.Global{
|
|
As: uint32(11111),
|
|
RouterId: ctrl.localIP.String(),
|
|
ListenPort: -1,
|
|
},
|
|
}); err != nil {
|
|
a.FailNow(err.Error())
|
|
}
|
|
defer ctrl.s.StopBgp(context.Background(), &api.StopBgpRequest{})
|
|
|
|
peer := &config.PeerConfig{
|
|
PeerIP: "10.10.10.1",
|
|
PeerAS: 22222,
|
|
MD5Password: tc.md5Password,
|
|
MD5EnvVar: tc.md5EnvVar,
|
|
}
|
|
|
|
err := ctrl.addPeer(peer)
|
|
a.NoError(err)
|
|
|
|
// Verify MD5 authentication is configured correctly
|
|
var foundPeer *api.Peer
|
|
ctrl.s.ListPeer(context.Background(), &api.ListPeerRequest{}, func(p *api.Peer) {
|
|
if p.Conf.NeighborAddress == peer.PeerIP {
|
|
foundPeer = p
|
|
}
|
|
})
|
|
|
|
a.NotNil(foundPeer, "Peer should be added")
|
|
a.Equal(tc.expectedAuth, foundPeer.Conf.AuthPassword, "MD5 password should match expected")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetMD5Password(t *testing.T) {
|
|
a := assert.New(t)
|
|
|
|
ctrl := &Controller{}
|
|
|
|
// Test 1: Environment variable takes priority
|
|
os.Setenv("TEST_BGP_PASS", "env_password")
|
|
defer os.Unsetenv("TEST_BGP_PASS")
|
|
|
|
peer := &config.PeerConfig{
|
|
MD5Password: "config_password",
|
|
MD5EnvVar: "TEST_BGP_PASS",
|
|
}
|
|
a.Equal("env_password", ctrl.getMD5Password(peer))
|
|
|
|
// Test 2: Config password when env var not set
|
|
peer2 := &config.PeerConfig{
|
|
MD5Password: "config_only",
|
|
MD5EnvVar: "NONEXISTENT_VAR",
|
|
}
|
|
a.Equal("config_only", ctrl.getMD5Password(peer2))
|
|
|
|
// Test 3: Empty string when nothing is set
|
|
peer3 := &config.PeerConfig{}
|
|
a.Equal("", ctrl.getMD5Password(peer3))
|
|
|
|
// Test 4: Only env var specified
|
|
os.Setenv("ANOTHER_PASS", "another_env_password")
|
|
defer os.Unsetenv("ANOTHER_PASS")
|
|
|
|
peer4 := &config.PeerConfig{
|
|
MD5EnvVar: "ANOTHER_PASS",
|
|
}
|
|
a.Equal("another_env_password", ctrl.getMD5Password(peer4))
|
|
}
|
|
|
|
func TestMultiPeerWithMD5(t *testing.T) {
|
|
a := assert.New(t)
|
|
|
|
// Set environment variables for testing
|
|
os.Setenv("PEER1_PASSWORD", "peer1_secret")
|
|
os.Setenv("PEER2_PASSWORD", "peer2_secret")
|
|
defer os.Unsetenv("PEER1_PASSWORD")
|
|
defer os.Unsetenv("PEER2_PASSWORD")
|
|
|
|
// Create controller with multiple peers using different MD5 configurations
|
|
multiPeerConfig := config.BgpConfig{
|
|
LocalAS: 11111,
|
|
LocalIP: "192.168.1.100",
|
|
Peers: []config.PeerConfig{
|
|
{
|
|
PeerIP: "10.10.10.1",
|
|
PeerAS: 22222,
|
|
MD5EnvVar: "PEER1_PASSWORD",
|
|
},
|
|
{
|
|
PeerIP: "10.10.10.2",
|
|
PeerAS: 33333,
|
|
MD5Password: "peer2_config_password",
|
|
},
|
|
{
|
|
PeerIP: "10.10.10.3",
|
|
PeerAS: 44444,
|
|
// No MD5 authentication
|
|
},
|
|
},
|
|
Origin: "igp",
|
|
}
|
|
|
|
ctrl, err := NewController(multiPeerConfig)
|
|
if err != nil {
|
|
a.FailNow(err.Error())
|
|
}
|
|
defer ctrl.Shutdown()
|
|
|
|
// Trigger peer addition by announcing a route (peers are added lazily)
|
|
_, ipnet, _ := net.ParseCIDR("20.30.40.0/24")
|
|
r := &Route{Net: ipnet}
|
|
ctrl.Announce(r)
|
|
|
|
// Verify all peers have correct MD5 configuration
|
|
peers := make(map[string]string) // map[peerIP]authPassword
|
|
ctrl.s.ListPeer(context.Background(), &api.ListPeerRequest{}, func(p *api.Peer) {
|
|
peers[p.Conf.NeighborAddress] = p.Conf.AuthPassword
|
|
})
|
|
|
|
a.Equal(3, len(peers), "Should have all three peers")
|
|
a.Equal("peer1_secret", peers["10.10.10.1"], "First peer should use env var password")
|
|
a.Equal("peer2_config_password", peers["10.10.10.2"], "Second peer should use config password")
|
|
a.Equal("", peers["10.10.10.3"], "Third peer should have no authentication")
|
|
}
|