Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
dc88a0d
update go mod for release
Mar 31, 2026
dd17daa
fix(go): remove dependency on go-deadlock pkg;
VishalDalwadi Apr 15, 2026
71d6786
fix(go): remove dependency on go-deadlock pkg; (#1292)
abhishek9686 Apr 20, 2026
b53593f
Merge pull request #1281 from gravitl/release-v1.5.1
abhishek9686 Apr 28, 2026
f9dfd7f
Merge pull request #1299 from gravitl/master
abhishek9686 Apr 28, 2026
1e2ad53
Merge pull request #1267 from gravitl/dependabot/go_modules/google.go…
dependabot[bot] May 15, 2026
559e7b0
build(deps): bump github.com/hashicorp/go-version from 1.8.0 to 1.9.0…
dependabot[bot] May 15, 2026
9c2b484
build(deps): bump golang.org/x/crypto from 0.49.0 to 0.50.0 (#1289)
dependabot[bot] May 15, 2026
1df4822
build(deps): bump alpine from 3.22.3 to 3.23.4 (#1296)
dependabot[bot] May 15, 2026
1268a7e
build(deps): bump dawidd6/action-download-artifact from 18 to 21 (#1303)
dependabot[bot] May 15, 2026
90b726a
build(deps): bump google.golang.org/grpc from 1.79.3 to 1.81.1 (#1285)
dependabot[bot] May 15, 2026
9d537fa
build(deps): bump github.com/jackc/pgx/v5 from 5.7.2 to 5.9.2 (#1298)
dependabot[bot] May 15, 2026
64c67af
build(deps): bump golang.org/x/term from 0.42.0 to 0.43.0 (#1290)
dependabot[bot] May 15, 2026
933d25a
NM-293: Nodes Schema Migration (#1304)
VishalDalwadi May 15, 2026
e05b246
NM-291: add egress LAN-to-VPN masquerade rule for iptables and nftabl…
abhishek9686 May 15, 2026
d767660
NM-306: Netclient DNS Issues (#1283)
VishalDalwadi May 15, 2026
510d287
fix(go): use sync.Map.Clear() to reset endpoint caches safely (#1301)
rsakaev-cloudlinux May 15, 2026
9bdd694
NM-304: Add MDM posture data collection, add posture check status
abhishek9686 May 18, 2026
8e18aa5
NM-300: trigger metrics collection (#1306)
abhishek9686 May 19, 2026
b5d84d1
build(deps): bump golang.org/x/crypto from 0.50.0 to 0.51.0 (#1310)
dependabot[bot] May 19, 2026
d8a5cd9
build(deps): bump golang.org/x/text from 0.36.0 to 0.37.0 (#1307)
dependabot[bot] May 19, 2026
0094700
build(deps): bump golang.zx2c4.com/wireguard/windows from 0.5.3 to 1.…
dependabot[bot] May 19, 2026
9d1ff24
build(deps): bump golang.org/x/net from 0.53.0 to 0.54.0 (#1309)
dependabot[bot] May 19, 2026
77eb420
Merge pull request #1311 from gravitl/NM-291
abhishek9686 May 30, 2026
7f307f3
NM-293: Null terminate interfaces; (#1314)
VishalDalwadi May 30, 2026
f720df2
NM-337: Filter routes only if it's an exact match (#1319)
VishalDalwadi May 30, 2026
448df86
NM-204: merge develop
abhishek9686 May 31, 2026
a5656f1
NM-304: set MDM posture fields on registration
abhishek9686 May 31, 2026
4ef83f0
build(deps): bump golang.org/x/crypto from 0.51.0 to 0.52.0 (#1318)
dependabot[bot] Jun 1, 2026
84390c6
build(deps): bump golang.org/x/sys from 0.44.0 to 0.45.0 (#1316)
dependabot[bot] Jun 1, 2026
d874a35
Fix/1302: skip reset config if iface does not exist; (#1312)
VishalDalwadi Jun 1, 2026
49ae196
NM-329: Improve Netclient UX on Register/Join. (#1305)
VishalDalwadi Jun 1, 2026
df55ca6
feat(go): move flow client to netmaker; (#1313)
VishalDalwadi Jun 1, 2026
ee9ef0c
Revert "build(deps): bump alpine from 3.22.3 to 3.23.4 (#1296)" (#1320)
abhishek9686 Jun 2, 2026
f924501
fix(go): loop over netclient peers and not device peers; (#1321)
VishalDalwadi Jun 5, 2026
bfbb1f0
fix(go): check pull data against igw monitor ip; (#1323)
VishalDalwadi Jun 11, 2026
04beaec
v1.6.0: release notes (#1324)
abhishek9686 Jun 12, 2026
8f8e8b3
NM-304: resolve merge conflicts
abhishek9686 Jun 16, 2026
c8daf9a
NM-304: fix Windows posture identity for workplace-joined hosts
abhishek9686 Jun 16, 2026
6c2aae4
NM-304: fix host identity collection
abhishek9686 Jun 17, 2026
e1c753f
NM-304: fix host identity collection
abhishek9686 Jun 17, 2026
9f6a247
NM-304: rm user email collection
abhishek9686 Jun 18, 2026
5cbf006
NM-304: update netmaker go mod
abhishek9686 Jun 18, 2026
826448f
Update functions/posture.go
abhishek9686 Jun 26, 2026
30c160c
Update auth/mdm.go
abhishek9686 Jun 26, 2026
b37c1e7
NM-304: resolve pr comments
abhishek9686 Jun 26, 2026
b8f48bf
Merge branch 'NM-304' of https://github.com/gravitl/netclient into NM…
abhishek9686 Jun 26, 2026
93f46a6
Merge pull request #1334 from gravitl/NM-304
abhishek9686 Jun 29, 2026
cd3afb8
resolve merge conflicts
abhishek9686 Jun 29, 2026
d2d2ab4
Merge pull request #1339 from gravitl/fix-master-conflicts
abhishek9686 Jun 29, 2026
0ae00d0
Merge remote-tracking branch 'upstream/develop'
claude Jul 13, 2026
a3787e7
Address PR review comments on upstream-sync merge
claude Jul 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/deletedroplets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: get logs
uses: dawidd6/action-download-artifact@v18
uses: dawidd6/action-download-artifact@v21
with:
run_id: ${{ github.event.workflow_run.id}}
if_no_artifact_found: warn
Expand Down Expand Up @@ -62,7 +62,7 @@ jobs:
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
steps:
- name: get logs
uses: dawidd6/action-download-artifact@v18
uses: dawidd6/action-download-artifact@v21
with:
run_id: ${{ github.event.workflow_run.id}}
if_no_artifact_found: warn
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<p align="center">
<a href="https://github.com/gravitl/netmaker/releases">
<img src="https://img.shields.io/badge/Version-1.5.1-informational?style=flat-square" />
<img src="https://img.shields.io/badge/Version-1.6.0-informational?style=flat-square" />
</a>
<a href="https://hub.docker.com/r/gravitl/netclient/tags">
<img src="https://img.shields.io/docker/pulls/gravitl/netclient?label=downloads" />
Expand Down
43 changes: 43 additions & 0 deletions auth/mdm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package auth

import (
"errors"
"fmt"
"net/http"
"strings"

"github.com/gravitl/netclient/ncutils"
)

// ErrMDMDenied is returned (via wrapping) when the netmaker server refuses a
// request because the host is not enrolled or compliant in the configured
// MDM. Callers should detect it with errors.Is and surface MDMDeniedMessage
// to the operator instead of a generic transport error.
var ErrMDMDenied = errors.New("device not enrolled or compliant in MDM")

// mdmDenialMarker is the case-insensitive substring the netmaker server
// includes in its error message when refusing on MDM grounds. The exact
// shape is owned by the server side; the substring is stable enough to use
// as a marker without depending on a specific phrasing.
const mdmDenialMarker = "mdm_posture"

// AsMDMDenied returns a wrapped ErrMDMDenied when err carries the well-known
// MDM denial signature (HTTP 403 + message containing "mdm_posture"), and
// otherwise returns err unchanged. The original ErrStatusNotOk remains in
// the wrap chain so existing transport handling still works.
func AsMDMDenied(err error) error {
if err == nil {
return nil
}
var notOk ncutils.ErrStatusNotOk
if !errors.As(err, &notOk) {
return err
}
if notOk.Status != http.StatusForbidden {
return err
}
if !strings.Contains(strings.ToLower(notOk.Message), mdmDenialMarker) {
return err
}
return fmt.Errorf("%w: %w", ErrMDMDenied, err)
}
58 changes: 58 additions & 0 deletions auth/mdm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package auth

import (
"errors"
"fmt"
"net/http"
"testing"

"github.com/gravitl/netclient/ncutils"
)

func TestAsMDMDeniedNil(t *testing.T) {
if got := AsMDMDenied(nil); got != nil {
t.Errorf("expected nil, got %v", got)
}
}

func TestAsMDMDeniedUnrelatedError(t *testing.T) {
in := errors.New("boom")
if got := AsMDMDenied(in); got != in {
t.Errorf("expected unchanged error, got %v", got)
}
}

func TestAsMDMDeniedNon403(t *testing.T) {
in := ncutils.ErrStatusNotOk{Status: http.StatusUnauthorized, Message: "mdm_posture_denied"}
got := AsMDMDenied(in)
if errors.Is(got, ErrMDMDenied) {
t.Errorf("non-403 should not be classified as MDM denial; got %v", got)
}
}

func TestAsMDMDenied403WithoutMarker(t *testing.T) {
in := ncutils.ErrStatusNotOk{Status: http.StatusForbidden, Message: "forbidden"}
got := AsMDMDenied(in)
if errors.Is(got, ErrMDMDenied) {
t.Errorf("403 without marker should pass through; got %v", got)
}
}

func TestAsMDMDenied403WithMarker(t *testing.T) {
in := ncutils.ErrStatusNotOk{Status: http.StatusForbidden, Message: "mdm_posture_denied"}
got := AsMDMDenied(in)
if !errors.Is(got, ErrMDMDenied) {
t.Fatalf("expected ErrMDMDenied, got %v", got)
}
}

func TestAsMDMDeniedThroughWrap(t *testing.T) {
wrapped := fmt.Errorf("send: %w", ncutils.ErrStatusNotOk{
Status: http.StatusForbidden,
Message: "MDM_POSTURE refused",
})
got := AsMDMDenied(wrapped)
if !errors.Is(got, ErrMDMDenied) {
t.Fatalf("expected ErrMDMDenied through wrap, got %v", got)
}
}
67 changes: 67 additions & 0 deletions cmd/posture.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
Copyright © 2026 Netmaker Team <info@netmaker.io>
*/
package cmd

import (
"encoding/json"
"fmt"
"os"

"github.com/gravitl/netclient/functions"
"github.com/gravitl/netclient/posture"
"github.com/spf13/cobra"
)

var postureCmd = &cobra.Command{
Use: "posture",
Short: "Inspect the host's posture status",
Long: `Subcommands for inspecting Netmaker posture-check evaluation results for this host.`,
}

var postureStatusCmd = &cobra.Command{
Use: "status",
Short: "Show the host's last-evaluated posture status",
Long: `Fetches the most recent posture status evaluated by the Netmaker server for this host.

The output includes the active MDM enrollment/compliance snapshot (when MDM is configured)
and per-network violation summaries.

Exit codes:
0 - all networks pass
1 - one or more networks have a 'fail' status (high severity violation)
2 - transport / authentication error`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
jsonOutput, _ := cmd.Flags().GetBool("json")
code, err := functions.PostureStatus(jsonOutput)
if err != nil {
fmt.Fprintln(os.Stderr, "posture status error:", err)
}
os.Exit(code)
},
}

var postureIdentityCmd = &cobra.Command{
Use: "identity",
Short: "Print locally collected device identity fields",
Long: `Shows hostname, serial, hardware UUID, and Entra device ID as
collected on this machine. Useful for verifying posture identity before check-in.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
id := posture.Collect()
b, err := json.MarshalIndent(id, "", " ")
if err != nil {
fmt.Fprintln(os.Stderr, "posture identity error:", err)
os.Exit(2)
}
fmt.Println(string(b))
},
}

func init() {
rootCmd.AddCommand(postureCmd)
postureCmd.AddCommand(postureStatusCmd)
postureCmd.AddCommand(postureIdentityCmd)
postureStatusCmd.Flags().BoolP("json", "j", false, "dump the raw JSON response")
}
8 changes: 4 additions & 4 deletions cmd/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"github.com/gravitl/netclient/functions"
"github.com/gravitl/netclient/ncutils"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/schema"
"github.com/spf13/cobra"
"golang.org/x/term"
)
Expand Down Expand Up @@ -109,15 +109,15 @@ func setHostFields(cmd *cobra.Command) {
config.Netclient().Interface = ifaceName
}
if firewall, err := cmd.Flags().GetString(registerFlags.Firewall); err == nil {
if ncutils.IsLinux() && (firewall == models.FIREWALL_IPTABLES || firewall == models.FIREWALL_NFTABLES) {
if ncutils.IsLinux() && (firewall == schema.FIREWALL_IPTABLES || firewall == schema.FIREWALL_NFTABLES) {
// check if firewall is present
if firewall == models.FIREWALL_IPTABLES {
if firewall == schema.FIREWALL_IPTABLES {
if !ncutils.IsIPTablesPresent() {
fmt.Println("iptables is not present")
os.Exit(1)
}
}
if firewall == models.FIREWALL_NFTABLES {
if firewall == schema.FIREWALL_NFTABLES {
if !ncutils.IsNFTablesPresent() {
fmt.Println("nftables is not present")
os.Exit(1)
Expand Down
7 changes: 5 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import (
"github.com/google/uuid"
"github.com/gravitl/netclient/config"
"github.com/gravitl/netclient/daemon"
"github.com/gravitl/netclient/functions"
"github.com/gravitl/netclient/ncutils"
"github.com/gravitl/netclient/posture"
"github.com/gravitl/netclient/wireguard"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
Expand Down Expand Up @@ -50,7 +50,7 @@ func Execute() {
}

func init() {
cobra.OnInitialize(functions.Migrate, initConfig)
cobra.OnInitialize(initConfig)
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
Expand Down Expand Up @@ -417,6 +417,9 @@ func checkConfig() {
saveRequired = true
config.SetFirewall()
}
if posture.ApplyIdentityIfChanged(&netclient.Host) {
saveRequired = true
}
if saveRequired {
logger.Log(3, "saving netclient configuration")
if err := config.WriteNetclientConfig(); err != nil {
Expand Down
28 changes: 16 additions & 12 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import (
"runtime"
"strconv"
"strings"
"sync"
"syscall"
"time"

"github.com/gravitl/netclient/ncutils"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/schema"
"github.com/sasha-s/go-deadlock"
"golang.zx2c4.com/wireguard/wgctrl"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
Expand Down Expand Up @@ -68,7 +68,7 @@ func (i InitType) String() string {
}

var (
netclientCfgMutex = &deadlock.RWMutex{}
netclientCfgMutex sync.RWMutex
netclient Config // netclient contains the netclient config
// Version - default version string
Version = "dev"
Expand Down Expand Up @@ -200,6 +200,10 @@ func UpdateHost(host *schema.Host) (resetInterface, restart, sendHostUpdate bool

// store password before updating
host.HostPass = hostCfg.HostPass
// Posture identity is client-authoritative; never accept server copies.
host.EntraDeviceID = hostCfg.EntraDeviceID
host.SerialNumber = hostCfg.SerialNumber
host.HardwareUUID = hostCfg.HardwareUUID
hostCfg.Host = *host
UpdateNetclient(*hostCfg)
WriteNetclientConfig()
Expand Down Expand Up @@ -513,27 +517,27 @@ func Convert(h *Config, n *Node) (models.Host, models.Node) {
// only auto-detects when no valid firewall has been configured.
func SetFirewall() {
if !ncutils.IsLinux() {
netclient.FirewallInUse = models.FIREWALL_NONE
netclient.FirewallInUse = schema.FIREWALL_NONE
return
}
// if user explicitly set a firewall and it's still present, keep it
switch netclient.FirewallInUse {
case models.FIREWALL_IPTABLES:
case schema.FIREWALL_IPTABLES:
if ncutils.IsIPTablesPresent() {
return
}
case models.FIREWALL_NFTABLES:
case schema.FIREWALL_NFTABLES:
if ncutils.IsNFTablesPresent() {
return
}
}
// auto-detect: user hasn't set one, or their choice is no longer available
if ncutils.IsIPTablesPresent() {
netclient.FirewallInUse = models.FIREWALL_IPTABLES
netclient.FirewallInUse = schema.FIREWALL_IPTABLES
} else if ncutils.IsNFTablesPresent() {
netclient.FirewallInUse = models.FIREWALL_NFTABLES
netclient.FirewallInUse = schema.FIREWALL_NFTABLES
} else {
netclient.FirewallInUse = models.FIREWALL_NONE
netclient.FirewallInUse = schema.FIREWALL_NONE
}
}

Expand All @@ -542,14 +546,14 @@ func SetFirewall() {
func FirewallHasChanged() bool {
if !ncutils.IsLinux() {
// non-Linux should always be FIREWALL_NONE; flag if not set
return netclient.FirewallInUse != models.FIREWALL_NONE
return netclient.FirewallInUse != schema.FIREWALL_NONE
}
switch netclient.FirewallInUse {
case models.FIREWALL_IPTABLES:
case schema.FIREWALL_IPTABLES:
return !ncutils.IsIPTablesPresent()
case models.FIREWALL_NFTABLES:
case schema.FIREWALL_NFTABLES:
return !ncutils.IsNFTablesPresent()
case models.FIREWALL_NONE:
case schema.FIREWALL_NONE:
// if a firewall becomes available, that's a change
return ncutils.IsIPTablesPresent() || ncutils.IsNFTablesPresent()
default:
Expand Down
Loading
Loading