mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-26 22:39:07 +01:00
100 lines
4.0 KiB
Go
100 lines
4.0 KiB
Go
//go:build darwin && !ios
|
|
|
|
package mdm
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"strings"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"howett.net/plist"
|
|
)
|
|
|
|
// policyPlistPath is the well-known location where macOS writes the
|
|
// device-level mandatory MDM payload for NetBird. The path is fixed by
|
|
// Apple convention: when an MDM provider (Jamf / Kandji / Mosyle /
|
|
// Intune for Mac / Workspace ONE) pushes a Configuration Profile that
|
|
// contains a com.apple.ManagedClient.preferences payload targeting the
|
|
// bundle id io.netbird.client, the OS materializes the payload here.
|
|
//
|
|
// Read-only — only the OS (root) is supposed to write this file. The
|
|
// loader sanity-checks the file mode and refuses to honour a world-
|
|
// writable plist, as a defense against tampered installs.
|
|
const policyPlistPath = "/Library/Managed Preferences/io.netbird.client.plist"
|
|
|
|
// loadPlatformPolicy reads the MDM-managed configuration from the macOS
|
|
// managed-preferences plist. Returns:
|
|
// - (nil, nil) when the plist is absent (device not MDM-enrolled for
|
|
// NetBird, or admin has not yet pushed a payload)
|
|
// - (map, nil) with N entries when N managed values are present
|
|
// (N may be 0 — empty plist still signals enrollment to the caller)
|
|
// - (nil, err) on permission / parse / safety errors
|
|
//
|
|
// Value-type coercion mirrors the Windows loader: native plist types
|
|
// map naturally onto the Policy accessor expectations (GetString /
|
|
// GetBool / GetInt / GetStringSlice). Unknown top-level keys are
|
|
// logged and skipped so a stray entry in the payload does not block
|
|
// loadPlatformPolicy reads the managed-preferences plist at policyPlistPath and returns recognised MDM key/value pairs.
|
|
//
|
|
// If the plist file does not exist, it returns (nil, nil). It returns a wrapped error on open/stat/decode failures.
|
|
// The function refuses to read a world-writable plist and returns an error in that case.
|
|
// loadPlatformPolicy reads the device-level managed-preferences plist and returns its recognized keys.
|
|
//
|
|
// It looks for the plist at policyPlistPath and, if present, decodes it into a map[string]any.
|
|
// Top-level plist keys are canonicalized case-insensitively to the package's internal MDM key names;
|
|
// unknown keys are logged and ignored. If the plist file does not exist, it returns (nil, nil).
|
|
// The function refuses to read the file if it is world-writable and returns a wrapped error for
|
|
// failures to open, stat, or decode the plist.
|
|
func loadPlatformPolicy() (map[string]any, error) {
|
|
f, err := os.Open(policyPlistPath)
|
|
if err != nil {
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
// Not enrolled for NetBird. Caller treats nil as
|
|
// "no MDM source present".
|
|
//nolint:nilnil // (nil, nil) is the documented platform-absent sentinel; see LoadPolicy.
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("open %s: %w", policyPlistPath, err)
|
|
}
|
|
defer func() {
|
|
if closeErr := f.Close(); closeErr != nil {
|
|
log.Warnf("MDM close plist %s: %v", policyPlistPath, closeErr)
|
|
}
|
|
}()
|
|
|
|
info, err := f.Stat()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("stat %s: %w", policyPlistPath, err)
|
|
}
|
|
// World-writable plist => tampered install. Refuse rather than
|
|
// honour potentially attacker-controlled policy values.
|
|
if info.Mode().Perm()&0o002 != 0 {
|
|
return nil, fmt.Errorf("refusing to read world-writable MDM source %s (mode %o)",
|
|
policyPlistPath, info.Mode().Perm())
|
|
}
|
|
|
|
raw := make(map[string]any)
|
|
if err := plist.NewDecoder(f).Decode(&raw); err != nil {
|
|
return nil, fmt.Errorf("decode plist %s: %w", policyPlistPath, err)
|
|
}
|
|
|
|
out := make(map[string]any, len(raw))
|
|
for name, val := range raw {
|
|
// macOS / AppConfig conventions both use camelCase for managed
|
|
// preferences keys; canonicalize to the mdm.Key* form so a key
|
|
// written as "ManagementURL" (PascalCase, rare on macOS but
|
|
// possible if the admin reused an ADMX-style name) still
|
|
// resolves.
|
|
canonical, known := canonicalKey[strings.ToLower(name)]
|
|
if !known {
|
|
log.Warnf("MDM ignoring unknown plist key %s: %s", policyPlistPath, name)
|
|
continue
|
|
}
|
|
out[canonical] = val
|
|
}
|
|
return out, nil
|
|
}
|