Compare commits

..

5 Commits

Author SHA1 Message Date
pascal
931598e593 fix authorized user groups for ssh + fix ignoring disabled policies + fix ignore invalid router 2026-08-12 17:43:43 +02:00
pascal
9bb1db28c3 Merge branch 'revert/component-types-test-suite' into revert/component-types 2026-08-12 16:55:06 +02:00
Dmitri Dolguikh
4952f2e8cf remove 'mage' tag from mage files
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-08-12 11:57:36 +02:00
Dmitri Dolguikh
32c7918c80 go mod tidy
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-08-12 11:50:31 +02:00
Dmitri Dolguikh
56d1607f72 added mage + targets for integration tests + updated github workflow
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-08-12 11:32:15 +02:00
10 changed files with 138 additions and 29 deletions

View File

@@ -729,6 +729,11 @@ jobs:
- name: Install modules
run: go mod tidy
- name: Run Mage
uses: magefile/mage-action@v4
with:
install-only: true
- name: check git status
run: git --no-pager diff --exit-code
@@ -737,9 +742,7 @@ jobs:
CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \
NETBIRD_STORE_ENGINE=${{ matrix.store }} \
CI=true \
go test -tags=integration -coverprofile=coverage.txt \
-exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' \
-timeout 20m ./management/server/http/...
mage integrationtest:all -gotestflags="-coverprofile=coverage.txt"
- name: Upload coverage reports to Codecov
if: matrix.arch == 'amd64'

1
go.mod
View File

@@ -76,6 +76,7 @@ require (
github.com/libp2p/go-nat v0.2.0
github.com/libp2p/go-netroute v0.4.0
github.com/lrh3321/ipset-go v0.0.0-20250619021614-54a0a98ace81
github.com/magefile/mage v1.17.2
github.com/mdlayher/socket v0.5.1
github.com/mdp/qrterminal/v3 v3.2.1
github.com/miekg/dns v1.1.72

2
go.sum
View File

@@ -415,6 +415,8 @@ github.com/lrh3321/ipset-go v0.0.0-20250619021614-54a0a98ace81 h1:J56rFEfUTFT9j9
github.com/lrh3321/ipset-go v0.0.0-20250619021614-54a0a98ace81/go.mod h1:RD8ML/YdXctQ7qbcizZkw5mZ6l8Ogrl1dodBzVJduwI=
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI=
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/magefile/mage v1.17.2 h1:fyXVu1eadI8Ap1HCCNgEhJ5McIWiYhLR8uol64ZZc40=
github.com/magefile/mage v1.17.2/go.mod h1:Yj51kqllmsgFpvvSzgrZPK9WtluG3kUhFaBUVLo4feA=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=

10
magefiles/magefile.go Normal file
View File

@@ -0,0 +1,10 @@
//mage:multiline
// Set the general description you want to have displayed with mage -l here.
package main
// mg contains helpful utility functions, like Deps
// Default target to run when none is specified
// If not set, running mage will list available targets
//var Default = Integrationtest.All

65
magefiles/test.go Normal file
View File

@@ -0,0 +1,65 @@
package main
import (
"errors"
"strings"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
)
var defaultcli = []string{"test", "-tags=integration", "-timeout=20m"}
type Integrationtest mg.Namespace
func (i Integrationtest) All(gotestflags *string) error {
var errs []error
if err := i.Api(gotestflags); err != nil {
errs = append(errs, err)
}
if err := i.NmapDb(gotestflags); err != nil {
errs = append(errs, err)
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (Integrationtest) NmapDb(gotestflags *string) error {
cli := defaultcli
if gotestflags != nil {
cli = append(cli, strings.Split(*gotestflags, " ")...)
}
cli = append(cli, "./integration_tests/management/network_map_db/...")
return sh.RunV("go", cli...)
}
func (Integrationtest) NmapDbPostgres(gotestflags *string) error {
cli := defaultcli
if gotestflags != nil {
cli = append(cli, strings.Split(*gotestflags, " ")...)
}
cli = append(cli, "./integration_tests/management/network_map_db/...")
return sh.RunWithV(map[string]string{"NETBIRD_STORE_ENGINE": "postgres"}, "go", cli...)
}
func (Integrationtest) NmapDbSqlite(gotestflags *string) error {
cli := defaultcli
if gotestflags != nil {
cli = append(cli, strings.Split(*gotestflags, " ")...)
}
cli = append(cli, "./integration_tests/management/network_map_db/...")
return sh.RunWithV(map[string]string{"NETBIRD_STORE_ENGINE": "sqlite"}, "go", cli...)
}
func (Integrationtest) Api(gotestflags *string) error {
cli := defaultcli
if gotestflags != nil {
cli = append(cli, strings.Split(*gotestflags, " ")...)
}
cli = append(cli, "./management/server/http/...")
return sh.RunV("go", cli...)
}

View File

@@ -173,10 +173,9 @@ func (e *componentEncoder) appendPeer(p *nmdata.Peer) uint32 {
return idx
}
// indexRouterPeers ensures every router peer is in the peer dedup table
// (c.RouterPeers may contain peers not in c.Peers when validation rules drop
// them) and returns their wire indexes for the RouterPeerIndexes field. Must
// run before any encoder that resolves peer ids via e.peerOrder.
// indexRouterPeers ensures every router peer is in the peer dedup table and
// returns their wire indexes for the RouterPeerIndexes field. Must run before
// any encoder that resolves peer ids via e.peerOrder.
func (e *componentEncoder) indexRouterPeers(routers map[string]*nmdata.Peer) []uint32 {
if len(routers) == 0 {
return nil
@@ -333,16 +332,15 @@ func unionPolicies(policies []*nmdata.Policy, resourcePolicies map[string][]*nmd
}
// encodeAuthorizedGroups translates rule.AuthorizedGroups (map keyed by
// group xid → local-user names) to the wire form (map keyed by group
// account_seq_id → UserNameList). Groups without a seq id are dropped —
// matches how source/destination group references handle the same case.
// group xid → local-user names) to the wire form (map keyed by
// authorizedGroupKey → UserNameList).
func (e *componentEncoder) encodeAuthorizedGroups(m map[string][]string) map[string]*proto.UserNameList {
if len(m) == 0 {
return nil
}
out := make(map[string]*proto.UserNameList, len(m))
for groupID, names := range m {
id, ok := e.groupPublicXid(groupID)
id, ok := e.authorizedGroupKey(groupID)
if !ok {
continue
}
@@ -351,6 +349,24 @@ func (e *componentEncoder) encodeAuthorizedGroups(m map[string][]string) map[str
return out
}
// authorizedGroupKey resolves the wire key for a group that grants SSH access.
// These are user groups: they hold no peers, so nothing puts them in
// components.Groups and groupPublicXid cannot see them. Dropping them the way a
// missing source/destination group is dropped would strip every authorized user
// from the envelope while PeerConfig still reports SSH enabled, leaving the peer
// running sshd with nobody able to log in — so the id is passed through instead.
// AuthorizedGroups and GroupIDToUserIDs are only ever used against each other,
// on both sides of the wire, so they just have to agree.
func (e *componentEncoder) authorizedGroupKey(groupID string) (string, bool) {
if groupID == "" {
return "", false
}
if id, ok := e.groupPublicXid(groupID); ok {
return id, true
}
return groupID, true
}
func (e *componentEncoder) groupPublicXid(groupID string) (string, bool) {
g, ok := e.components.Groups[groupID]
if !ok {
@@ -650,7 +666,7 @@ func (e *componentEncoder) encodeGroupIDToUserIDs(m map[string][]string) map[str
}
out := make(map[string]*proto.UserIDList, len(m))
for groupID, userIDs := range m {
id, ok := e.groupPublicXid(groupID)
id, ok := e.authorizedGroupKey(groupID)
if !ok || len(userIDs) == 0 {
continue
}

View File

@@ -697,15 +697,20 @@ func TestEncodeNetworkMapEnvelope_RouterPeerNotInComponentsPeers(t *testing.T) {
func TestEncodeNetworkMapEnvelope_GroupIDToUserIDs(t *testing.T) {
c := newTestComponents()
c.GroupIDToUserIDs = map[string][]string{
"group-src": {"user-1", "user-2"},
"group-missing": {"user-4"}, // group not in components → drop
"group-src": {"user-1", "user-2"},
"group-users": {"user-4"},
}
full := EncodeNetworkMapEnvelope(ComponentsEnvelopeInput{Components: c}).GetFull()
require.Len(t, full.GroupIdToUserIds, 1, "only present groups survive")
require.Len(t, full.GroupIdToUserIds, 2,
"a peer group is keyed by its public id, and a user group — which never appears in "+
"components.Groups — keeps its own id rather than being dropped, or the peer would "+
"receive no authorized SSH users at all")
require.Contains(t, full.GroupIdToUserIds, "1")
assert.ElementsMatch(t, []string{"user-1", "user-2"}, full.GroupIdToUserIds["1"].UserIds)
require.Contains(t, full.GroupIdToUserIds, "group-users")
assert.ElementsMatch(t, []string{"user-4"}, full.GroupIdToUserIds["group-users"].UserIds)
}
func TestToProxyPatch_EmptyInputReturnsNil(t *testing.T) {

View File

@@ -105,7 +105,7 @@ func (nmd *NetworkMapData) GetPeerNetworkMapComponents(peerID string, peersCusto
}
for _, policy := range policies {
if policy == nil || len(policy.Rules) == 0 || policy.Rules[0] == nil {
if policy == nil || !policy.Enabled || len(policy.Rules) == 0 || policy.Rules[0] == nil {
continue
}
if addSourcePeers {
@@ -147,7 +147,7 @@ func (nmd *NetworkMapData) GetPeerNetworkMapComponents(peerID string, peersCusto
}
for _, rule := range policy.Rules {
if rule == nil {
if rule == nil || !rule.Enabled {
continue
}
for _, srcGroupID := range rule.Sources {
@@ -171,15 +171,21 @@ func (nmd *NetworkMapData) GetPeerNetworkMapComponents(peerID string, peersCusto
if addSourcePeers {
components.RoutersMap[resource.NetworkID] = networkRoutingPeers
for peerIDKey := range networkRoutingPeers {
if p := nmd.Peers[peerIDKey]; p != nil {
if _, exists := components.RouterPeers[peerIDKey]; !exists {
components.RouterPeers[peerIDKey] = p
}
if _, exists := components.Peers[peerIDKey]; !exists {
if _, validated := nmd.ValidatedPeers[peerIDKey]; validated {
components.Peers[peerIDKey] = p
}
}
p := nmd.Peers[peerIDKey]
if p == nil {
continue
}
// An unapproved peer must not carry traffic, so it is kept out of
// RouterPeers as well: the envelope encoder indexes that map into
// the wire peer table, from which the client restores every entry.
if _, validated := nmd.ValidatedPeers[peerIDKey]; !validated {
continue
}
if _, exists := components.RouterPeers[peerIDKey]; !exists {
components.RouterPeers[peerIDKey] = p
}
if _, exists := components.Peers[peerIDKey]; !exists {
components.Peers[peerIDKey] = p
}
}
components.NetworkResources = append(components.NetworkResources, resource)

View File

@@ -1103,8 +1103,9 @@ func TestGetPeerNetworkMapComponents_NetworkResources_SourceSide(t *testing.T) {
assert.Equal(t, []*nmdata.NetworkResource{res}, c.NetworkResources)
assert.Equal(t, map[string][]*nmdata.Policy{"res-1": {rp}}, c.ResourcePoliciesMap)
assert.Equal(t, map[string]map[string]*nmdata.NetworkRouter{"net-1": routers}, c.RoutersMap)
assert.ElementsMatch(t, []string{routerOK.ID, routerUnval.ID}, peerIDSet(c.RouterPeers),
"RouterPeers carries all routing peers regardless of validation")
assert.ElementsMatch(t, []string{routerOK.ID}, peerIDSet(c.RouterPeers),
"an unvalidated routing peer is withheld from RouterPeers too, since the envelope encoder "+
"indexes that map into the wire peer table and the client restores every entry from it")
assert.ElementsMatch(t, []string{targetID, routerOK.ID}, peerIDSet(c.Peers),
"only validated routing peers are connected")
assert.ElementsMatch(t, []string{"g-clients", "g-resource"}, groupIDSet(c.Groups))

View File

@@ -819,7 +819,7 @@ func (c *NetworkMapComponents) processResourcePolicies(
var routes []*nmdata.Route
for _, policy := range c.ResourcePoliciesMap[resource.ID] {
if policy == nil || len(policy.Rules) == 0 || policy.Rules[0] == nil {
if policy == nil || !policy.Enabled || len(policy.Rules) == 0 || policy.Rules[0] == nil {
continue
}
peers := c.getResourcePolicyPeers(policy)