diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 551241f5..93be810c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 1 - - uses: WillAbides/setup-go-faster@v1.8.0 + - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go }} - run: "go test -race ./..." diff --git a/cmd/cli/ad_windows.go b/cmd/cli/ad_windows.go index 66180a90..c477b4eb 100644 --- a/cmd/cli/ad_windows.go +++ b/cmd/cli/ad_windows.go @@ -10,11 +10,12 @@ import ( hh "github.com/microsoft/wmi/pkg/hardware/host" "github.com/Control-D-Inc/ctrld" + "github.com/Control-D-Inc/ctrld/internal/system" ) // addExtraSplitDnsRule adds split DNS rule for domain if it's part of active directory. func addExtraSplitDnsRule(cfg *ctrld.Config) bool { - domain, err := getActiveDirectoryDomain() + domain, err := system.GetActiveDirectoryDomain() if err != nil { mainLog.Load().Debug().Msgf("unable to get active directory domain: %v", err) return false diff --git a/cmd/cli/ad_windows_test.go b/cmd/cli/ad_windows_test.go index 6abd25f9..c987fe13 100644 --- a/cmd/cli/ad_windows_test.go +++ b/cmd/cli/ad_windows_test.go @@ -5,14 +5,16 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/Control-D-Inc/ctrld" + "github.com/Control-D-Inc/ctrld/internal/system" "github.com/Control-D-Inc/ctrld/testhelper" - "github.com/stretchr/testify/assert" ) func Test_getActiveDirectoryDomain(t *testing.T) { start := time.Now() - domain, err := getActiveDirectoryDomain() + domain, err := system.GetActiveDirectoryDomain() if err != nil { t.Fatal(err) } diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go index 7d70b78b..49d8b674 100644 --- a/cmd/cli/cli.go +++ b/cmd/cli/cli.go @@ -642,13 +642,19 @@ func processCDFlags(cfg *ctrld.Config) (*controld.ResolverConfig, error) { logger.Info().Msgf("fetching Controld D configuration from API: %s", cdUID) bo := backoff.NewBackoff("processCDFlags", logf, 30*time.Second) bo.LogLongerThan = 30 * time.Second + ctx := context.Background() - resolverConfig, err := controld.FetchResolverConfig(cdUID, rootCmd.Version, cdDev) + req := &controld.ResolverConfigRequest{ + RawUID: cdUID, + Version: rootCmd.Version, + Metadata: ctrld.SystemMetadata(ctx), + } + resolverConfig, err := controld.FetchResolverConfig(req, cdDev) for { if errUrlNetworkError(err) { bo.BackOff(ctx, err) logger.Warn().Msg("could not fetch resolver using bootstrap DNS, retrying...") - resolverConfig, err = controld.FetchResolverConfig(cdUID, rootCmd.Version, cdDev) + resolverConfig, err = controld.FetchResolverConfig(req, cdDev) continue } break @@ -1516,7 +1522,12 @@ func cdUIDFromProvToken() string { if customHostname != "" && !validHostname(customHostname) { mainLog.Load().Fatal().Msgf("invalid custom hostname: %q", customHostname) } - req := &controld.UtilityOrgRequest{ProvToken: cdOrg, Hostname: customHostname} + + req := &controld.UtilityOrgRequest{ + ProvToken: cdOrg, + Hostname: customHostname, + Metadata: ctrld.SystemMetadata(context.Background()), + } // Process provision token if provided. resolverConfig, err := controld.FetchResolverUID(req, rootCmd.Version, cdDev) if err != nil { @@ -1857,7 +1868,12 @@ func runningIface(s service.Service) *ifaceResponse { // doValidateCdRemoteConfig fetches and validates custom config for cdUID. func doValidateCdRemoteConfig(cdUID string, fatal bool) error { - rc, err := controld.FetchResolverConfig(cdUID, rootCmd.Version, cdDev) + req := &controld.ResolverConfigRequest{ + RawUID: cdUID, + Version: rootCmd.Version, + Metadata: ctrld.SystemMetadata(context.Background()), + } + rc, err := controld.FetchResolverConfig(req, cdDev) if err != nil { logger := mainLog.Load().Fatal() if !fatal { diff --git a/cmd/cli/control_server.go b/cmd/cli/control_server.go index 9281b904..3db444e2 100644 --- a/cmd/cli/control_server.go +++ b/cmd/cli/control_server.go @@ -217,7 +217,12 @@ func (p *prog) registerControlServerHandler() { } // Re-fetch pin code from API. - if rc, err := controld.FetchResolverConfig(cdUID, rootCmd.Version, cdDev); rc != nil { + rcReq := &controld.ResolverConfigRequest{ + RawUID: cdUID, + Version: rootCmd.Version, + Metadata: ctrld.SystemMetadata(context.Background()), + } + if rc, err := controld.FetchResolverConfig(rcReq, cdDev); rc != nil { if rc.DeactivationPin != nil { cdDeactivationPin.Store(*rc.DeactivationPin) } else { diff --git a/cmd/cli/dns_proxy.go b/cmd/cli/dns_proxy.go index 994741b1..ba9d5af5 100644 --- a/cmd/cli/dns_proxy.go +++ b/cmd/cli/dns_proxy.go @@ -954,7 +954,13 @@ func (p *prog) doSelfUninstall(answer *dns.Msg) { logger := mainLog.Load().With().Str("mode", "self-uninstall").Logger() if p.refusedQueryCount > selfUninstallMaxQueries { p.checkingSelfUninstall = true - _, err := controld.FetchResolverConfig(cdUID, rootCmd.Version, cdDev) + + req := &controld.ResolverConfigRequest{ + RawUID: cdUID, + Version: rootCmd.Version, + Metadata: ctrld.SystemMetadata(context.Background()), + } + _, err := controld.FetchResolverConfig(req, cdDev) logger.Debug().Msg("maximum number of refused queries reached, checking device status") selfUninstallCheck(err, p, logger) diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index cf8c6804..42e2efe0 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -325,7 +325,12 @@ func (p *prog) apiConfigReload() { } doReloadApiConfig := func(forced bool, logger zerolog.Logger) { - resolverConfig, err := controld.FetchResolverConfig(cdUID, rootCmd.Version, cdDev) + req := &controld.ResolverConfigRequest{ + RawUID: cdUID, + Version: rootCmd.Version, + Metadata: ctrld.SystemMetadata(context.Background()), + } + resolverConfig, err := controld.FetchResolverConfig(req, cdDev) selfUninstallCheck(err, p, logger) if err != nil { logger.Warn().Err(err).Msg("could not fetch resolver config") diff --git a/docs/runtime-internal-logging.md b/docs/runtime-internal-logging.md new file mode 100644 index 00000000..982632cb --- /dev/null +++ b/docs/runtime-internal-logging.md @@ -0,0 +1,46 @@ +# Runtime Internal Logging + +When no logging is configured (i.e., `log_path` is not set), ctrld automatically enables an internal logging system. This system stores logs in memory to provide troubleshooting information when problems occur. + +## Purpose + +The runtime internal logging system is designed primarily for **ctrld developers**, not end users. It captures detailed diagnostic information that can be useful for troubleshooting issues when they arise, especially in production environments where explicit logging may not be configured. + +## When It's Enabled + +Internal logging is automatically enabled when: + +- ctrld is running in Control D mode (i.e., `--cd` flag is provided) +- No log file is configured (i.e., `log_path` is empty or not set) + +If a log file is explicitly configured via `log_path`, internal logging will **not** be enabled, as the configured log file serves the logging purpose. + +## How It Works + +The internal logging system: + +- Stores logs in **in-memory buffers** (not written to disk) +- Captures logs at **debug level** for normal operations and **warn level** for warnings +- Maintains separate buffers for normal logs and warning logs +- Automatically manages buffer size to prevent unbounded memory growth +- Preserves initialization logs even when buffers overflow + +## Configuration + +**Important**: The `log_level` configuration option does **not** affect the internal logging system. Internal logging always operates at debug level for normal logs and warn level for warnings, regardless of the `log_level` setting in the configuration file. + +The `log_level` setting only affects: +- Console output (when running interactively) +- File-based logging (when `log_path` is configured) + +## Accessing Internal Logs + +Internal logs can be accessed through the control server API endpoints. This functionality is intended for developers and support personnel who need to diagnose issues. + +## Notes + +- Internal logging is **not** a replacement for proper log file configuration in production environments +- For production deployments, it is recommended to configure `log_path` to enable persistent file-based logging +- Internal logs are stored in memory and will be lost if the process terminates unexpectedly +- The internal logging system is automatically disabled when explicit logging is configured + diff --git a/doh.go b/doh.go index 3459cb8a..58aaf165 100644 --- a/doh.go +++ b/doh.go @@ -122,11 +122,6 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro } if err != nil { err = wrapUrlError(err) - if r.isDoH3 { - if closer, ok := c.Transport.(io.Closer); ok { - closer.Close() - } - } return nil, fmt.Errorf("could not perform request: %w", err) } defer resp.Body.Close() diff --git a/go.mod b/go.mod index a171a51c..56015734 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.24 require ( github.com/Masterminds/semver/v3 v3.2.1 github.com/ameshkov/dnsstamps v1.0.3 + github.com/brunogui0812/sysprofiler v0.5.0 github.com/coreos/go-systemd/v22 v22.5.0 github.com/cuonglm/osinfo v0.0.0-20230921071424-e0e1b1e0bbbf github.com/docker/go-units v0.5.0 @@ -15,6 +16,7 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.1 github.com/illarion/gonotify/v2 v2.0.3 github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 + github.com/jaypipes/ghw v0.21.0 github.com/jaytaylor/go-hostsfile v0.0.0-20220426042432-61485ac1fa6c github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 github.com/kardianos/service v1.2.1 @@ -27,12 +29,12 @@ require ( github.com/prometheus/client_golang v1.19.1 github.com/prometheus/client_model v0.5.0 github.com/prometheus/prom2json v1.3.3 - github.com/quic-go/quic-go v0.56.0 + github.com/quic-go/quic-go v0.57.1 github.com/rs/zerolog v1.28.0 - github.com/spf13/cobra v1.8.1 - github.com/spf13/pflag v1.0.5 + github.com/spf13/cobra v1.9.1 + github.com/spf13/pflag v1.0.6 github.com/spf13/viper v1.16.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.11.1 github.com/vishvananda/netlink v1.2.1-beta.2 golang.org/x/net v0.43.0 golang.org/x/sync v0.16.0 @@ -55,8 +57,10 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/groob/plist v0.0.0-20200425180238-0f631f258c01 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jaypipes/pcidb v1.1.1 // indirect github.com/jsimonetti/rtnetlink v1.4.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect @@ -75,15 +79,17 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect - github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/qpack v0.6.0 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/spakin/awk v1.0.0 // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect github.com/vishvananda/netns v0.0.4 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/crypto v0.41.0 // indirect @@ -94,6 +100,7 @@ require ( google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + howett.net/plist v1.0.2-0.20250314012144-ee69052608d9 // indirect ) replace github.com/mr-karan/doggo => github.com/Windscribe/doggo v0.0.0-20220919152748-2c118fc391f8 diff --git a/go.sum b/go.sum index 2f13cf37..266a916a 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,8 @@ github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1O github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/brunogui0812/sysprofiler v0.5.0 h1:AUekplOKG/VKH6sPSBRxsKOA9Uv5OsI8qolXM73dXPU= +github.com/brunogui0812/sysprofiler v0.5.0/go.mod h1:lLd7gvylgd4nsTSC8exq1YY6qhLWXkgnalxjVzdlbEM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -64,7 +66,7 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cuonglm/osinfo v0.0.0-20230921071424-e0e1b1e0bbbf h1:40DHYsri+d1bnroFDU2FQAeq68f3kAlOzlQ93kCf26Q= github.com/cuonglm/osinfo v0.0.0-20230921071424-e0e1b1e0bbbf/go.mod h1:G45410zMgmnSjLVKCq4f6GpbYAzoP2plX9rPwgx6C24= @@ -91,6 +93,7 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg= github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= @@ -165,6 +168,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/groob/plist v0.0.0-20200425180238-0f631f258c01 h1:0T3XGXebqLj7zSVLng9wX9axQzTEnvj/h6eT7iLfUas= +github.com/groob/plist v0.0.0-20200425180238-0f631f258c01/go.mod h1:itkABA+w2cw7x5nYUS/pLRef6ludkZKOigbROmCTaFw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= @@ -181,8 +186,13 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= +github.com/jaypipes/ghw v0.21.0 h1:ClG2xWtYY0c1ud9jZYwVGdSgfCI7AbmZmZyw3S5HHz8= +github.com/jaypipes/ghw v0.21.0/go.mod h1:GPrvwbtPoxYUenr74+nAnWbardIZq600vJDD5HnPsPE= +github.com/jaypipes/pcidb v1.1.1 h1:QmPhpsbmmnCwZmHeYAATxEaoRuiMAJusKYkUncMC0ro= +github.com/jaypipes/pcidb v1.1.1/go.mod h1:x27LT2krrUgjf875KxQXKB0Ha/YXLdZRVmw6hH0G7g8= github.com/jaytaylor/go-hostsfile v0.0.0-20220426042432-61485ac1fa6c h1:kbTQ8oGf+BVFvt/fM+ECI+NbZDCqoi0vtZTfB2p2hrI= github.com/jaytaylor/go-hostsfile v0.0.0-20220426042432-61485ac1fa6c/go.mod h1:k6+89xKz7BSMJ+DzIerBdtpEUeTlBMugO/hcVSzahog= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= @@ -259,10 +269,10 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/prom2json v1.3.3 h1:IYfSMiZ7sSOfliBoo89PcufjWO4eAR0gznGcETyaUgo= github.com/prometheus/prom2json v1.3.3/go.mod h1:Pv4yIPktEkK7btWsrUTWDDDrnpUrAELaOCj+oFwlgmc= -github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= -github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.56.0 h1:q/TW+OLismmXAehgFLczhCDTYB3bFmua4D9lsNBWxvY= -github.com/quic-go/quic-go v0.56.0/go.mod h1:9gx5KsFQtw2oZ6GZTyh+7YEvOxWCL9WZAepnHxgAo6c= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10= +github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -274,16 +284,18 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spakin/awk v1.0.0 h1:5ulBVgJhdN3XoFGNVv/MOHOIUfPVPvMCIlLH6O6ZqU4= +github.com/spakin/awk v1.0.0/go.mod h1:e7FnxcIEcRqdKwStPYWonox4n9DpharWk+3nnn1IqJs= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -299,8 +311,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw= @@ -314,6 +326,8 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -439,6 +453,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -661,6 +676,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +howett.net/plist v1.0.2-0.20250314012144-ee69052608d9 h1:eeH1AIcPvSc0Z25ThsYF+Xoqbn0CI/YnXVYoTLFdGQw= +howett.net/plist v1.0.2-0.20250314012144-ee69052608d9/go.mod h1:fyFX5Hj5tP1Mpk8obqA9MZgXT416Q5711SDT7dQLTLk= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/controld/config.go b/internal/controld/config.go index 595e758e..b6e7047f 100644 --- a/internal/controld/config.go +++ b/internal/controld/config.go @@ -71,14 +71,23 @@ func (u ErrorResponse) Error() string { } type utilityRequest struct { - UID string `json:"uid"` - ClientID string `json:"client_id,omitempty"` + UID string `json:"uid"` + ClientID string `json:"client_id,omitempty"` + Metadata map[string]string `json:"metadata"` } // UtilityOrgRequest contains request data for calling Org API. type UtilityOrgRequest struct { - ProvToken string `json:"prov_token"` - Hostname string `json:"hostname"` + ProvToken string `json:"prov_token"` + Hostname string `json:"hostname"` + Metadata map[string]string `json:"metadata"` +} + +// ResolverConfigRequest contains request data for fetching resolver config. +type ResolverConfigRequest struct { + RawUID string + Version string + Metadata map[string]string } // LogsRequest contains request data for sending runtime logs to API. @@ -88,26 +97,30 @@ type LogsRequest struct { } // FetchResolverConfig fetch Control D config for given uid. -func FetchResolverConfig(rawUID, version string, cdDev bool) (*ResolverConfig, error) { - uid, clientID := ParseRawUID(rawUID) - req := utilityRequest{UID: uid} +func FetchResolverConfig(req *ResolverConfigRequest, cdDev bool) (*ResolverConfig, error) { + uid, clientID := ParseRawUID(req.RawUID) + uReq := utilityRequest{ + UID: uid, + Metadata: req.Metadata, + } if clientID != "" { - req.ClientID = clientID + uReq.ClientID = clientID } - body, _ := json.Marshal(req) - return postUtilityAPI(version, cdDev, false, bytes.NewReader(body)) + body, _ := json.Marshal(uReq) + return postUtilityAPI(req.Version, cdDev, false, bytes.NewReader(body)) } -// FetchResolverUID fetch resolver uid from provision token. +// FetchResolverUID fetch resolver uid from a given request. func FetchResolverUID(req *UtilityOrgRequest, version string, cdDev bool) (*ResolverConfig, error) { if req == nil { return nil, errors.New("invalid request") } - hostname := req.Hostname - if hostname == "" { - hostname, _ = os.Hostname() + if req.Hostname == "" { + hostname, _ := os.Hostname() + req.Hostname = hostname } - body, _ := json.Marshal(UtilityOrgRequest{ProvToken: req.ProvToken, Hostname: hostname}) + + body, _ := json.Marshal(req) return postUtilityAPI(version, cdDev, false, bytes.NewReader(body)) } diff --git a/internal/controld/controld_test.go b/internal/controld/controld_test.go index 2c00247d..80762e36 100644 --- a/internal/controld/controld_test.go +++ b/internal/controld/controld_test.go @@ -3,10 +3,13 @@ package controld import ( + "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/Control-D-Inc/ctrld" ) func TestFetchResolverConfig(t *testing.T) { @@ -20,11 +23,18 @@ func TestFetchResolverConfig(t *testing.T) { {"valid dev", "p2", true, false}, {"invalid uid", "abcd1234", false, true}, } + + ctx := context.Background() for _, tc := range tests { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() - got, err := FetchResolverConfig(tc.uid, "dev-test", tc.dev) + req := &ResolverConfigRequest{ + RawUID: tc.uid, + Version: "dev-test", + Metadata: ctrld.SystemMetadata(ctx), + } + got, err := FetchResolverConfig(ctx, req, tc.dev) require.False(t, (err != nil) != tc.wantErr, err) if !tc.wantErr { assert.NotEmpty(t, got.DOH) diff --git a/internal/system/chassis_darwin.go b/internal/system/chassis_darwin.go new file mode 100644 index 00000000..49a7317b --- /dev/null +++ b/internal/system/chassis_darwin.go @@ -0,0 +1,25 @@ +package system + +import ( + "errors" + "fmt" + + "github.com/brunogui0812/sysprofiler" +) + +// GetChassisInfo retrieves hardware information including machine model type and vendor from the system profiler. +func GetChassisInfo() (*ChassisInfo, error) { + hardwares, err := sysprofiler.Hardware() + if err != nil { + return nil, fmt.Errorf("failed to get hardware info: %w", err) + } + if len(hardwares) == 0 { + return nil, errors.New("no hardware info found") + } + hardware := hardwares[0] + info := &ChassisInfo{ + Type: hardware.MachineModel, + Vendor: "Apple Inc.", + } + return info, nil +} diff --git a/internal/system/chassis_others.go b/internal/system/chassis_others.go new file mode 100644 index 00000000..49e38aaa --- /dev/null +++ b/internal/system/chassis_others.go @@ -0,0 +1,20 @@ +//go:build !darwin + +package system + +import "github.com/jaypipes/ghw" + +// GetChassisInfo retrieves hardware information including machine model type and vendor from the system profiler. +func GetChassisInfo() (*ChassisInfo, error) { + // Disable warnings from ghw, since these are undesirable but recoverable errors. + // With warnings enabled, ghw will emit unnecessary log messages. + chassis, err := ghw.Chassis(ghw.WithDisableWarnings()) + if err != nil { + return nil, err + } + info := &ChassisInfo{ + Type: chassis.TypeDescription, + Vendor: chassis.Vendor, + } + return info, nil +} diff --git a/internal/system/metadata.go b/internal/system/metadata.go new file mode 100644 index 00000000..cfe02e3c --- /dev/null +++ b/internal/system/metadata.go @@ -0,0 +1,7 @@ +package system + +// ChassisInfo represents the structural framework of a device, specifying its type and manufacturer information. +type ChassisInfo struct { + Type string + Vendor string +} diff --git a/internal/system/metadata_others.go b/internal/system/metadata_others.go new file mode 100644 index 00000000..f20a1508 --- /dev/null +++ b/internal/system/metadata_others.go @@ -0,0 +1,8 @@ +//go:build !windows + +package system + +// GetActiveDirectoryDomain returns AD domain name of this computer. +func GetActiveDirectoryDomain() (string, error) { + return "", nil +} diff --git a/internal/system/metadata_windows.go b/internal/system/metadata_windows.go new file mode 100644 index 00000000..40f137fb --- /dev/null +++ b/internal/system/metadata_windows.go @@ -0,0 +1,74 @@ +package system + +import ( + "errors" + "fmt" + "io" + "log" + "os" + "strings" + "unsafe" + + "github.com/microsoft/wmi/pkg/base/host" + hh "github.com/microsoft/wmi/pkg/hardware/host" + "golang.org/x/sys/windows" +) + +// GetActiveDirectoryDomain returns AD domain name of this computer. +func GetActiveDirectoryDomain() (string, error) { + log.SetOutput(io.Discard) + defer log.SetOutput(os.Stderr) + + // 1) Check environment variable + envDomain := os.Getenv("USERDNSDOMAIN") + if envDomain != "" { + return strings.TrimSpace(envDomain), nil + } + + // 2) Query WMI via the microsoft/wmi library + whost := host.NewWmiLocalHost() + cs, err := hh.GetComputerSystem(whost) + if cs != nil { + defer cs.Close() + } + if err != nil { + return "", err + } + pod, err := cs.GetPropertyPartOfDomain() + if err != nil { + return "", err + } + if pod { + domainVal, err := cs.GetPropertyDomain() + if err != nil { + return "", fmt.Errorf("failed to get domain property: %w", err) + } + domainName := strings.TrimSpace(fmt.Sprintf("%v", domainVal)) + if domainName == "" { + return "", errors.New("machine does not appear to have a domain set") + } + return domainName, nil + } + return "", nil +} + +// DomainJoinedStatus returns the domain joined status of the current computer. +// +// NETSETUP_JOIN_STATUS constants from Microsoft Windows API +// See: https://learn.microsoft.com/en-us/windows/win32/api/lmjoin/ne-lmjoin-netsetup_join_status +// +// NetSetupUnknownStatus uint32 = 0 // The status is unknown +// NetSetupUnjoined uint32 = 1 // The computer is not joined to a domain or workgroup +// NetSetupWorkgroupName uint32 = 2 // The computer is joined to a workgroup +// NetSetupDomainName uint32 = 3 // The computer is joined to a domain +func DomainJoinedStatus() (uint32, error) { + var domain *uint16 + var status uint32 + + if err := windows.NetGetJoinInformation(nil, &domain, &status); err != nil { + return 0, fmt.Errorf("failed to get domain join status: %w", err) + } + defer windows.NetApiBufferFree((*byte)(unsafe.Pointer(domain))) + + return status, nil +} diff --git a/metadata.go b/metadata.go new file mode 100644 index 00000000..11e8fb51 --- /dev/null +++ b/metadata.go @@ -0,0 +1,78 @@ +package ctrld + +import ( + "context" + "os" + "os/user" + + "github.com/cuonglm/osinfo" + + "github.com/Control-D-Inc/ctrld/internal/system" +) + +const ( + metadataOsKey = "os" + metadataChassisTypeKey = "chassis_type" + metadataChassisVendorKey = "chassis_vendor" + metadataUsernameKey = "username" + metadataDomainOrWorkgroupKey = "domain_or_workgroup" + metadataDomainKey = "domain" +) + +var ( + chassisType string + chassisVendor string +) + +// SystemMetadata collects system and user-related SystemMetadata and returns it as a map. +func SystemMetadata(ctx context.Context) map[string]string { + m := make(map[string]string) + oi := osinfo.New() + m[metadataOsKey] = oi.String() + if chassisType == "" && chassisVendor == "" { + if ci, err := system.GetChassisInfo(); err == nil { + chassisType, chassisVendor = ci.Type, ci.Vendor + } + } + m[metadataChassisTypeKey] = chassisType + m[metadataChassisVendorKey] = chassisVendor + m[metadataUsernameKey] = currentLoginUser(ctx) + m[metadataDomainOrWorkgroupKey] = partOfDomainOrWorkgroup(ctx) + domain, err := system.GetActiveDirectoryDomain() + if err != nil { + ProxyLogger.Load().Debug().Err(err).Msg("Failed to get active directory domain name") + } + m[metadataDomainKey] = domain + + return m +} + +// currentLoginUser attempts to find the actual login user, even if the process is running as root. +func currentLoginUser(ctx context.Context) string { + // 1. Check SUDO_USER: This is the most reliable way to find the original user + // when a script is run via 'sudo'. + if sudoUser := os.Getenv("SUDO_USER"); sudoUser != "" { + return sudoUser + } + + // 2. Check general user login variables. LOGNAME is often preferred over USER. + if logName := os.Getenv("LOGNAME"); logName != "" { + return logName + } + + // 3. Fallback to USER variable. + if userEnv := os.Getenv("USER"); userEnv != "" { + return userEnv + } + + // 4. Final fallback: Use the standard library function to get the *effective* user. + // This will return "root" if the process is running as root. + currentUser, err := user.Current() + if err != nil { + // Handle error gracefully, returning a placeholder + ProxyLogger.Load().Debug().Err(err).Msg("Failed to get current user") + return "unknown" + } + + return currentUser.Username +} diff --git a/metadata_others.go b/metadata_others.go new file mode 100644 index 00000000..2b060ac3 --- /dev/null +++ b/metadata_others.go @@ -0,0 +1,10 @@ +//go:build !windows + +package ctrld + +import "context" + +// partOfDomainOrWorkgroup checks if the computer is part of a domain or workgroup and returns "true" or "false". +func partOfDomainOrWorkgroup(ctx context.Context) string { + return "false" +} diff --git a/metadata_test.go b/metadata_test.go new file mode 100644 index 00000000..b832c7e8 --- /dev/null +++ b/metadata_test.go @@ -0,0 +1,11 @@ +package ctrld + +import ( + "context" + "testing" +) + +func Test_metadata(t *testing.T) { + m := SystemMetadata(context.Background()) + t.Logf("metadata: %v", m) +} diff --git a/metadata_windows.go b/metadata_windows.go new file mode 100644 index 00000000..e00dfe48 --- /dev/null +++ b/metadata_windows.go @@ -0,0 +1,22 @@ +package ctrld + +import ( + "context" + + "github.com/Control-D-Inc/ctrld/internal/system" +) + +// partOfDomainOrWorkgroup checks if the computer is part of a domain or workgroup and returns "true" or "false". +func partOfDomainOrWorkgroup(ctx context.Context) string { + status, err := system.DomainJoinedStatus() + if err != nil { + ProxyLogger.Load().Debug().Err(err).Msg("Failed to get domain join status") + return "false" + } + switch status { + case 2, 3: + return "true" + default: + return "false" + } +} diff --git a/nameservers_windows.go b/nameservers_windows.go index 7b16e8e1..68173931 100644 --- a/nameservers_windows.go +++ b/nameservers_windows.go @@ -20,6 +20,8 @@ import ( "golang.org/x/sys/windows" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "tailscale.com/net/netmon" + + "github.com/Control-D-Inc/ctrld/internal/system" ) const ( @@ -128,7 +130,7 @@ func getDNSServers(ctx context.Context) ([]string, error) { var dcServers []string isDomain := checkDomainJoined() if isDomain { - domainName, err := getLocalADDomain() + domainName, err := system.GetActiveDirectoryDomain() if err != nil { Log(context.Background(), logger.Debug(), "Failed to get local AD domain: %v", err) @@ -337,75 +339,18 @@ func currentNameserversFromResolvconf() []string { func checkDomainJoined() bool { logger := *ProxyLogger.Load() - var domain *uint16 - var status uint32 - - if err := windows.NetGetJoinInformation(nil, &domain, &status); err != nil { - Log(context.Background(), logger.Debug(), "Failed to get domain join status: %v", err) + status, err := system.DomainJoinedStatus() + if err != nil { + logger.Debug().Msgf("Failed to get domain joined status: %v", err) return false } - defer windows.NetApiBufferFree((*byte)(unsafe.Pointer(domain))) - - // NETSETUP_JOIN_STATUS constants from Microsoft Windows API - // See: https://learn.microsoft.com/en-us/windows/win32/api/lmjoin/ne-lmjoin-netsetup_join_status - // - // NetSetupUnknownStatus uint32 = 0 // The status is unknown - // NetSetupUnjoined uint32 = 1 // The computer is not joined to a domain or workgroup - // NetSetupWorkgroupName uint32 = 2 // The computer is joined to a workgroup - // NetSetupDomainName uint32 = 3 // The computer is joined to a domain - // - // We only care about NetSetupDomainName. - domainName := windows.UTF16PtrToString(domain) - Log(context.Background(), logger.Debug(), - "Domain join status: domain=%s status=%d (UnknownStatus=0, Unjoined=1, WorkgroupName=2, DomainName=3)", - domainName, status) - isDomain := status == syscall.NetSetupDomainName - Log(context.Background(), logger.Debug(), "Is domain joined? status=%d, result=%v", status, isDomain) + logger.Debug().Msg("Domain join status: (UnknownStatus=0, Unjoined=1, WorkgroupName=2, DomainName=3)") + logger.Debug().Msgf("Is domain joined? status=%d, result=%v", status, isDomain) return isDomain } -// getLocalADDomain uses Microsoft's WMI wrappers (github.com/microsoft/wmi/pkg/*) -// to query the Domain field from Win32_ComputerSystem instead of a direct go-ole call. -func getLocalADDomain() (string, error) { - log.SetOutput(io.Discard) - defer log.SetOutput(os.Stderr) - // 1) Check environment variable - envDomain := os.Getenv("USERDNSDOMAIN") - if envDomain != "" { - return strings.TrimSpace(envDomain), nil - } - - // 2) Query WMI via the microsoft/wmi library - whost := host.NewWmiLocalHost() - q := query.NewWmiQuery("Win32_ComputerSystem") - instances, err := instance.GetWmiInstancesFromHost(whost, string(constant.CimV2), q) - if instances != nil { - defer instances.Close() - } - if err != nil { - return "", fmt.Errorf("WMI query failed: %v", err) - } - - // If no results, return an error - if len(instances) == 0 { - return "", fmt.Errorf("no rows returned from Win32_ComputerSystem") - } - - // We only care about the first row - domainVal, err := instances[0].GetProperty("Domain") - if err != nil { - return "", fmt.Errorf("machine does not appear to have a domain set: %v", err) - } - - domainName := strings.TrimSpace(fmt.Sprintf("%v", domainVal)) - if domainName == "" { - return "", fmt.Errorf("machine does not appear to have a domain set") - } - return domainName, nil -} - // validInterfaces returns a list of all physical interfaces. // this is a duplicate of what is in net_windows.go, we should // clean this up so there is only one version