mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-09 16:31:29 +02:00
A policy that enforces a management URL refuses any SetConfig or Login whose URL differs from it. The comparison normalized only the default port, so three ways of writing the very endpoint the policy names were reported as conflicts: policy https://mgmt.example.com vs https://mgmt.example.com/ refused https://MGMT.example.com refused https://mgmt.example.com:0443 refused For an MDM-managed deployment whose stored or command-line URL is spelled differently from the policy's value, that means every settings update is refused with an MDMManagedFieldsViolation naming a field the caller did not change. `netbird up --management-url https://MGMT.example.com` reproduces it. The rules now live in util.SameServiceURL, and ConflictURL delegates: scheme and host compared case-insensitively, the effective port normalized numerically, a trailing slash ignored, and a path otherwise still part of the identity so /other remains a divergence. Unparseable input falls back to string equality. util rather than either caller, because comparing two service URLs is neither device management nor profile storage, and more than one place does it: an MDM-enforced management URL against a requested one here, a stored profile URL against a command-line one in profilemanager and the SSH gate. Every copy of these rules that drifts turns an equivalent URL into a refused request, which is how this one arose. CanonicalURL is left alone: besides comparison it is the canonical value handed to mdm.Restrictions and to the Android and iOS Preferences getters, and normalizing what those return is a separate decision.
117 lines
2.8 KiB
Go
117 lines
2.8 KiB
Go
package mdm
|
|
|
|
import (
|
|
"net/url"
|
|
|
|
"github.com/netbirdio/netbird/util"
|
|
)
|
|
|
|
// PreSharedKeyRedactedSentinel is the redaction mask returned in place of a
|
|
// real pre-shared key; an incoming value equal to it is a round-trip echo,
|
|
// never an override.
|
|
const PreSharedKeyRedactedSentinel = "**********"
|
|
|
|
// ConflictCheck is a value-aware comparison between a single requested field
|
|
// and the corresponding MDM-enforced value.
|
|
type ConflictCheck struct {
|
|
Key string
|
|
Check func(*Policy) bool
|
|
}
|
|
|
|
// ConflictBool builds a ConflictCheck for a boolean MDM key.
|
|
func ConflictBool(key string, p *bool) ConflictCheck {
|
|
return ConflictCheck{
|
|
Key: key,
|
|
Check: func(pol *Policy) bool {
|
|
if p == nil {
|
|
return true
|
|
}
|
|
want, ok := pol.GetBool(key)
|
|
return ok && want == *p
|
|
},
|
|
}
|
|
}
|
|
|
|
// ConflictStringPtr builds a ConflictCheck for an optional string MDM key,
|
|
// where an explicit empty value is still a request to change the setting. A
|
|
// nil p means "field not set" (no override requested).
|
|
func ConflictStringPtr(key string, p *string) ConflictCheck {
|
|
return ConflictCheck{
|
|
Key: key,
|
|
Check: func(pol *Policy) bool {
|
|
if p == nil {
|
|
return true
|
|
}
|
|
want, ok := pol.GetString(key)
|
|
return ok && want == *p
|
|
},
|
|
}
|
|
}
|
|
|
|
// ConflictURL builds a ConflictCheck for a URL-typed MDM key. The two sides are
|
|
// compared as the endpoints they address, not as strings: see
|
|
// util.SameServiceURL.
|
|
func ConflictURL(key, got string) ConflictCheck {
|
|
return ConflictCheck{
|
|
Key: key,
|
|
Check: func(pol *Policy) bool {
|
|
if got == "" {
|
|
return true
|
|
}
|
|
want, ok := pol.GetString(key)
|
|
return ok && util.SameServiceURLStrings(want, got)
|
|
},
|
|
}
|
|
}
|
|
|
|
// ConflictInt64 builds a ConflictCheck for an integer MDM key.
|
|
func ConflictInt64(key string, p *int64) ConflictCheck {
|
|
return ConflictCheck{
|
|
Key: key,
|
|
Check: func(pol *Policy) bool {
|
|
if p == nil {
|
|
return true
|
|
}
|
|
want, ok := pol.GetInt(key)
|
|
return ok && want == *p
|
|
},
|
|
}
|
|
}
|
|
|
|
// ResolveConflicts returns the names of keys whose requested value diverges
|
|
// from the policy-enforced value; keys the policy does not manage are skipped,
|
|
// a managed key without a Check counts as a conflict.
|
|
func ResolveConflicts(policy *Policy, checks []ConflictCheck) []string {
|
|
if policy.IsEmpty() {
|
|
return nil
|
|
}
|
|
var conflicts []string
|
|
for _, c := range checks {
|
|
if !policy.HasKey(c.Key) {
|
|
continue
|
|
}
|
|
if c.Check == nil || !c.Check(policy) {
|
|
conflicts = append(conflicts, c.Key)
|
|
}
|
|
}
|
|
return conflicts
|
|
}
|
|
|
|
// CanonicalURL normalizes a service URL by appending the scheme default port
|
|
// when none is present; unparseable input is returned unchanged.
|
|
func CanonicalURL(s string) string {
|
|
u, err := url.ParseRequestURI(s)
|
|
if err != nil {
|
|
return s
|
|
}
|
|
if u.Port() == "" {
|
|
switch u.Scheme {
|
|
case "https":
|
|
u.Host += ":443"
|
|
case "http":
|
|
u.Host += ":80"
|
|
}
|
|
}
|
|
return u.String()
|
|
}
|