mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-16 11:39:57 +00:00
236 lines
7.7 KiB
Go
236 lines
7.7 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"os/user"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/grpc/codes"
|
|
gstatus "google.golang.org/grpc/status"
|
|
|
|
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
|
"github.com/netbirdio/netbird/client/mdm"
|
|
"github.com/netbirdio/netbird/client/proto"
|
|
)
|
|
|
|
// withMDMPolicy temporarily overrides the server-package loadMDMPolicy hook
|
|
// so SetConfig observes the supplied Policy. Restores the original loader
|
|
// at test cleanup.
|
|
func withMDMPolicy(t *testing.T, policy *mdm.Policy) {
|
|
t.Helper()
|
|
prev := loadMDMPolicy
|
|
loadMDMPolicy = func() *mdm.Policy { return policy }
|
|
t.Cleanup(func() { loadMDMPolicy = prev })
|
|
}
|
|
|
|
// setupServerWithProfile mirrors the boilerplate of TestSetConfig_AllFieldsSaved:
|
|
// overrides profilemanager paths to a temp dir, seeds a profile, sets it
|
|
// active, and constructs a Server instance. Returns the constructed server
|
|
// plus context + profile name + username + cfgPath for the seeded profile.
|
|
func setupServerWithProfile(t *testing.T) (s *Server, ctx context.Context, profName, username, cfgPath string) {
|
|
t.Helper()
|
|
tempDir := t.TempDir()
|
|
|
|
origDefaultProfileDir := profilemanager.DefaultConfigPathDir
|
|
origDefaultConfigPath := profilemanager.DefaultConfigPath
|
|
origActiveProfileStatePath := profilemanager.ActiveProfileStatePath
|
|
profilemanager.ConfigDirOverride = tempDir
|
|
profilemanager.DefaultConfigPathDir = tempDir
|
|
profilemanager.ActiveProfileStatePath = tempDir + "/active_profile.json"
|
|
profilemanager.DefaultConfigPath = filepath.Join(tempDir, "default.json")
|
|
t.Cleanup(func() {
|
|
profilemanager.DefaultConfigPathDir = origDefaultProfileDir
|
|
profilemanager.ActiveProfileStatePath = origActiveProfileStatePath
|
|
profilemanager.DefaultConfigPath = origDefaultConfigPath
|
|
profilemanager.ConfigDirOverride = ""
|
|
})
|
|
|
|
currUser, err := user.Current()
|
|
require.NoError(t, err)
|
|
|
|
profName = "test-profile-mdm"
|
|
cfgPath = filepath.Join(tempDir, profName+".json")
|
|
|
|
_, err = profilemanager.UpdateOrCreateConfig(profilemanager.ConfigInput{
|
|
ConfigPath: cfgPath,
|
|
ManagementURL: "https://api.netbird.io:443",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
pm := profilemanager.ServiceManager{}
|
|
require.NoError(t, pm.SetActiveProfileState(&profilemanager.ActiveProfileState{
|
|
ID: profilemanager.ID(profName),
|
|
Username: currUser.Username,
|
|
}))
|
|
|
|
ctx = context.Background()
|
|
s = New(ctx, "console", "", false, false, false, false)
|
|
return s, ctx, profName, currUser.Username, cfgPath
|
|
}
|
|
|
|
// extractViolation pulls the MDMManagedFieldsViolation detail from a
|
|
// FailedPrecondition error. Fails the test if absent or malformed.
|
|
func extractViolation(t *testing.T, err error) *proto.MDMManagedFieldsViolation {
|
|
t.Helper()
|
|
require.Error(t, err)
|
|
st, ok := gstatus.FromError(err)
|
|
require.True(t, ok, "error must be a gRPC status: %v", err)
|
|
require.Equal(t, codes.FailedPrecondition, st.Code(), "expected FailedPrecondition, got %s", st.Code())
|
|
for _, d := range st.Details() {
|
|
if v, ok := d.(*proto.MDMManagedFieldsViolation); ok {
|
|
return v
|
|
}
|
|
}
|
|
t.Fatalf("MDMManagedFieldsViolation detail not found on status; details: %v", st.Details())
|
|
return nil
|
|
}
|
|
|
|
func TestSetConfig_MDMReject_SingleField(t *testing.T) {
|
|
withMDMPolicy(t, mdm.NewPolicy(map[string]any{
|
|
mdm.KeyManagementURL: "https://mdm.example.com:443",
|
|
}))
|
|
|
|
s, ctx, profName, username, _ := setupServerWithProfile(t)
|
|
|
|
_, err := s.SetConfig(ctx, &proto.SetConfigRequest{
|
|
ProfileName: profName,
|
|
Username: username,
|
|
ManagementUrl: "https://user.tried.this.com:443",
|
|
})
|
|
|
|
v := extractViolation(t, err)
|
|
assert.Equal(t, []string{mdm.KeyManagementURL}, v.GetFields())
|
|
}
|
|
|
|
func TestSetConfig_MDMReject_MultipleFields(t *testing.T) {
|
|
withMDMPolicy(t, mdm.NewPolicy(map[string]any{
|
|
mdm.KeyManagementURL: "https://mdm.example.com:443",
|
|
mdm.KeyBlockInbound: true,
|
|
mdm.KeyRosenpassEnabled: true,
|
|
}))
|
|
|
|
s, ctx, profName, username, _ := setupServerWithProfile(t)
|
|
|
|
blockInbound := false
|
|
rosenpassEnabled := false
|
|
_, err := s.SetConfig(ctx, &proto.SetConfigRequest{
|
|
ProfileName: profName,
|
|
Username: username,
|
|
ManagementUrl: "https://user.tried.this.com:443",
|
|
BlockInbound: &blockInbound,
|
|
RosenpassEnabled: &rosenpassEnabled,
|
|
})
|
|
|
|
v := extractViolation(t, err)
|
|
assert.ElementsMatch(t, []string{
|
|
mdm.KeyManagementURL,
|
|
mdm.KeyBlockInbound,
|
|
mdm.KeyRosenpassEnabled,
|
|
}, v.GetFields())
|
|
}
|
|
|
|
func TestSetConfig_MDMReject_AllOrNothing(t *testing.T) {
|
|
// MDM enforces ManagementURL only; user request touches both the
|
|
// enforced field AND a non-enforced field (RosenpassEnabled).
|
|
// The whole request must be rejected — non-conflicting fields are not
|
|
// applied either.
|
|
withMDMPolicy(t, mdm.NewPolicy(map[string]any{
|
|
mdm.KeyManagementURL: "https://mdm.example.com:443",
|
|
}))
|
|
|
|
s, ctx, profName, username, cfgPath := setupServerWithProfile(t)
|
|
|
|
rosenpassEnabled := true
|
|
_, err := s.SetConfig(ctx, &proto.SetConfigRequest{
|
|
ProfileName: profName,
|
|
Username: username,
|
|
ManagementUrl: "https://user.tried.this.com:443",
|
|
RosenpassEnabled: &rosenpassEnabled,
|
|
})
|
|
|
|
v := extractViolation(t, err)
|
|
assert.Equal(t, []string{mdm.KeyManagementURL}, v.GetFields())
|
|
|
|
// Confirm RosenpassEnabled was NOT applied even though it was not
|
|
// in the conflict list: the request was rejected as a whole.
|
|
reloaded, err := profilemanager.GetConfig(cfgPath)
|
|
require.NoError(t, err)
|
|
assert.False(t, reloaded.RosenpassEnabled, "non-conflicting field must not be applied when request is rejected")
|
|
}
|
|
|
|
func TestSetConfig_MDMAllow_NonManagedFields(t *testing.T) {
|
|
// MDM enforces ManagementURL but the user only writes RosenpassEnabled.
|
|
// Request must succeed.
|
|
withMDMPolicy(t, mdm.NewPolicy(map[string]any{
|
|
mdm.KeyManagementURL: "https://mdm.example.com:443",
|
|
}))
|
|
|
|
s, ctx, profName, username, _ := setupServerWithProfile(t)
|
|
|
|
rosenpassEnabled := true
|
|
resp, err := s.SetConfig(ctx, &proto.SetConfigRequest{
|
|
ProfileName: profName,
|
|
Username: username,
|
|
RosenpassEnabled: &rosenpassEnabled,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
}
|
|
|
|
// TestSetConfig_MDMAllow_ManagementURLPortNormalized covers the
|
|
// regression from discussion #6483: MDM URL without explicit port vs
|
|
// UI echo with the parseURL-appended default port must be treated as
|
|
// a no-op echo, not a conflict.
|
|
func TestSetConfig_MDMAllow_ManagementURLPortNormalized(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mdmURL string
|
|
submitURL string
|
|
}{
|
|
{"policy_no_port_submit_with_443", "https://netbird.corp.example", "https://netbird.corp.example:443"},
|
|
{"policy_with_443_submit_no_port", "https://netbird.corp.example:443", "https://netbird.corp.example"},
|
|
{"http_policy_no_port_submit_with_80", "http://netbird.corp.example", "http://netbird.corp.example:80"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
withMDMPolicy(t, mdm.NewPolicy(map[string]any{
|
|
mdm.KeyManagementURL: tc.mdmURL,
|
|
}))
|
|
|
|
s, ctx, profName, username, _ := setupServerWithProfile(t)
|
|
|
|
rosenpassEnabled := true
|
|
resp, err := s.SetConfig(ctx, &proto.SetConfigRequest{
|
|
ProfileName: profName,
|
|
Username: username,
|
|
ManagementUrl: tc.submitURL,
|
|
RosenpassEnabled: &rosenpassEnabled,
|
|
})
|
|
|
|
require.NoError(t, err, "port-normalized URL echo must not trip MDM conflict gate")
|
|
require.NotNil(t, resp)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSetConfig_MDMEmpty_NoEnforcement(t *testing.T) {
|
|
// No MDM policy active: any field can be written.
|
|
withMDMPolicy(t, mdm.NewPolicy(nil))
|
|
|
|
s, ctx, profName, username, _ := setupServerWithProfile(t)
|
|
|
|
resp, err := s.SetConfig(ctx, &proto.SetConfigRequest{
|
|
ProfileName: profName,
|
|
Username: username,
|
|
ManagementUrl: "https://user.changed.url.com:443",
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
}
|