Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add process posture check #1693

Merged
merged 30 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5f0eec0
wip: add process check posture
bcmmbaga Mar 12, 2024
9f41a1f
add process posture check to posture checks handlers
bcmmbaga Mar 12, 2024
e66e39c
Extend peer metadata with processes
bcmmbaga Mar 12, 2024
41348bb
Add process validation for peer metadata
bcmmbaga Mar 12, 2024
60f9f08
fix tests
bcmmbaga Mar 13, 2024
cc60df7
Allow set of single unix or windows path check
bcmmbaga Mar 14, 2024
9db450d
Add single Unix/Windows path check in process tests
bcmmbaga Mar 14, 2024
1a5d59b
Refactor
bcmmbaga Mar 14, 2024
4ab993c
Fix tests
bcmmbaga Mar 14, 2024
90ab2f7
Fix linters
bcmmbaga Mar 14, 2024
180f5a1
Refactor posture check validations (#1705)
bcmmbaga Mar 14, 2024
9dcaa51
Merge branch 'main' into add-process-posture-check
bcmmbaga Mar 18, 2024
2727680
Merge branch 'main' into add-process-posture-check
bcmmbaga Mar 21, 2024
36582d1
Merge branch 'refs/heads/main' into add-process-posture-check
bcmmbaga Apr 10, 2024
c6ab215
Extend management to sync meta and posture checks with peer (#1727)
bcmmbaga Apr 15, 2024
8aa32a2
Merge branch 'refs/heads/main' into add-process-posture-check
bcmmbaga Apr 15, 2024
6bfd1b2
fix merge conflicts
bcmmbaga Apr 15, 2024
7745ed7
Merge branch 'refs/heads/main' into add-process-posture-check
bcmmbaga Apr 17, 2024
0d93677
Merge branch 'refs/heads/main' into add-process-posture-check
bcmmbaga Apr 24, 2024
e4f5466
go mod tidy
bcmmbaga Apr 24, 2024
2d3fd1f
split unix path into a linux and mac path (#1893)
bcmmbaga Apr 30, 2024
17aef4f
Merge branch 'refs/heads/main' into add-process-posture-check
bcmmbaga May 13, 2024
0c0925e
Fix peer sync metadata
bcmmbaga May 13, 2024
0939660
Merge branch 'refs/heads/main' into add-process-posture-check
bcmmbaga May 22, 2024
645b9fe
go mod tidy
bcmmbaga May 22, 2024
59b1c0f
merge 0.28.0
pascal-fischer May 31, 2024
1ec5e66
remove comment from merge
pascal-fischer May 31, 2024
e7a0fd8
Fix SonarCloud issues (#2096)
bcmmbaga Jun 6, 2024
aff6ae1
Merge branch 'refs/heads/0.28.0' into add-process-posture-check
bcmmbaga Jun 6, 2024
142a3d0
Regenerate mgmnt protocol messages
bcmmbaga Jun 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion client/internal/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,10 @@ func (c *ConnectClient) run(
return wrapErr(err)
}

checks := loginResp.GetChecks()

c.engineMutex.Lock()
c.engine = NewEngineWithProbes(engineCtx, cancel, signalClient, mgmClient, engineConfig, mobileDependency, c.statusRecorder, mgmProbe, signalProbe, relayProbe, wgProbe)
c.engine = NewEngineWithProbes(engineCtx, cancel, signalClient, mgmClient, engineConfig, mobileDependency, c.statusRecorder, mgmProbe, signalProbe, relayProbe, wgProbe, checks)
c.engineMutex.Unlock()

err = c.engine.Start()
Expand Down
52 changes: 50 additions & 2 deletions client/internal/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net/netip"
"reflect"
"runtime"
"slices"
"strings"
"sync"
"time"
Expand All @@ -30,6 +31,7 @@ import (
"github.com/netbirdio/netbird/client/internal/routemanager"
"github.com/netbirdio/netbird/client/internal/wgproxy"
nbssh "github.com/netbirdio/netbird/client/ssh"
"github.com/netbirdio/netbird/client/system"
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/iface"
"github.com/netbirdio/netbird/iface/bind"
Expand Down Expand Up @@ -157,6 +159,9 @@ type Engine struct {
wgProbe *Probe

wgConnWorker sync.WaitGroup

// checks are the client-applied posture checks that need to be evaluated on the client
checks []*mgmProto.Checks
}

// Peer is an instance of the Connection Peer
Expand All @@ -174,6 +179,7 @@ func NewEngine(
config *EngineConfig,
mobileDep MobileDependency,
statusRecorder *peer.Status,
checks []*mgmProto.Checks,
) *Engine {
return NewEngineWithProbes(
clientCtx,
Expand All @@ -187,6 +193,7 @@ func NewEngine(
nil,
nil,
nil,
checks,
)
}

Expand All @@ -203,6 +210,7 @@ func NewEngineWithProbes(
signalProbe *Probe,
relayProbe *Probe,
wgProbe *Probe,
checks []*mgmProto.Checks,
) *Engine {

return &Engine{
Expand All @@ -223,6 +231,7 @@ func NewEngineWithProbes(
signalProbe: signalProbe,
relayProbe: relayProbe,
wgProbe: wgProbe,
checks: checks,
}
}

Expand Down Expand Up @@ -530,14 +539,38 @@ func (e *Engine) handleSync(update *mgmProto.SyncResponse) error {
// todo update signal
}

if err := e.updateChecksIfNew(update.Checks); err != nil {
return err
}

if update.GetNetworkMap() != nil {
// only apply new changes and ignore old ones
err := e.updateNetworkMap(update.GetNetworkMap())
if err != nil {
return err
}
}
return nil
}

// updateChecksIfNew updates checks if there are changes and sync new meta with management
func (e *Engine) updateChecksIfNew(checks []*mgmProto.Checks) error {
// if checks are equal, we skip the update
if isChecksEqual(e.checks, checks) {
return nil
}
e.checks = checks

info, err := system.GetInfoWithChecks(e.ctx, checks)
if err != nil {
log.Warnf("failed to get system info with checks: %v", err)
info = system.GetInfo(e.ctx)
}

if err := e.mgmClient.SyncMeta(info); err != nil {
log.Errorf("could not sync meta: error %s", err)
return err
}
return nil
}

Expand Down Expand Up @@ -627,7 +660,14 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error {
// E.g. when a new peer has been registered and we are allowed to connect to it.
func (e *Engine) receiveManagementEvents() {
go func() {
err := e.mgmClient.Sync(e.ctx, e.handleSync)
info, err := system.GetInfoWithChecks(e.ctx, e.checks)
if err != nil {
log.Warnf("failed to get system info with checks: %v", err)
info = system.GetInfo(e.ctx)
}

// err = e.mgmClient.Sync(info, e.handleSync)
err = e.mgmClient.Sync(e.ctx, info, e.handleSync)
if err != nil {
// happens if management is unavailable for a long time.
// We want to cancel the operation of the whole client
Expand Down Expand Up @@ -1216,7 +1256,8 @@ func (e *Engine) close() {
}

func (e *Engine) readInitialSettings() ([]*route.Route, *nbdns.Config, error) {
netMap, err := e.mgmClient.GetNetworkMap()
info := system.GetInfo(e.ctx)
netMap, err := e.mgmClient.GetNetworkMap(info)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -1442,3 +1483,10 @@ func (e *Engine) startNetworkMonitor() {
}
}()
}

// isChecksEqual checks if two slices of checks are equal.
func isChecksEqual(checks []*mgmProto.Checks, oChecks []*mgmProto.Checks) bool {
return slices.EqualFunc(checks, oChecks, func(checks, oChecks *mgmProto.Checks) bool {
return slices.Equal(checks.Files, oChecks.Files)
})
}
14 changes: 7 additions & 7 deletions client/internal/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func TestEngine_SSH(t *testing.T) {
WgPrivateKey: key,
WgPort: 33100,
ServerSSHAllowed: true,
}, MobileDependency{}, peer.NewRecorder("https://mgm"))
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil)

engine.dnsServer = &dns.MockServer{
UpdateDNSServerFunc: func(serial uint64, update nbdns.Config) error { return nil },
Expand Down Expand Up @@ -211,7 +211,7 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
WgAddr: "100.64.0.1/24",
WgPrivateKey: key,
WgPort: 33100,
}, MobileDependency{}, peer.NewRecorder("https://mgm"))
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil)
newNet, err := stdnet.NewNet()
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -393,7 +393,7 @@ func TestEngine_Sync(t *testing.T) {
// feed updates to Engine via mocked Management client
updates := make(chan *mgmtProto.SyncResponse)
defer close(updates)
syncFunc := func(ctx context.Context, msgHandler func(msg *mgmtProto.SyncResponse) error) error {
syncFunc := func(ctx context.Context, info *system.Info, msgHandler func(msg *mgmtProto.SyncResponse) error) error {
for msg := range updates {
err := msgHandler(msg)
if err != nil {
Expand All @@ -408,7 +408,7 @@ func TestEngine_Sync(t *testing.T) {
WgAddr: "100.64.0.1/24",
WgPrivateKey: key,
WgPort: 33100,
}, MobileDependency{}, peer.NewRecorder("https://mgm"))
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil)
engine.ctx = ctx

engine.dnsServer = &dns.MockServer{
Expand Down Expand Up @@ -567,7 +567,7 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
WgAddr: wgAddr,
WgPrivateKey: key,
WgPort: 33100,
}, MobileDependency{}, peer.NewRecorder("https://mgm"))
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil)
engine.ctx = ctx
newNet, err := stdnet.NewNet()
if err != nil {
Expand Down Expand Up @@ -737,7 +737,7 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) {
WgAddr: wgAddr,
WgPrivateKey: key,
WgPort: 33100,
}, MobileDependency{}, peer.NewRecorder("https://mgm"))
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil)
engine.ctx = ctx

newNet, err := stdnet.NewNet()
Expand Down Expand Up @@ -1008,7 +1008,7 @@ func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey strin
WgPort: wgPort,
}

e, err := NewEngine(ctx, cancel, signalClient, mgmtClient, conf, MobileDependency{}, peer.NewRecorder("https://mgm")), nil
e, err := NewEngine(ctx, cancel, signalClient, mgmtClient, conf, MobileDependency{}, peer.NewRecorder("https://mgm"), nil), nil
e.ctx = ctx
return e, err
}
Expand Down
26 changes: 26 additions & 0 deletions client/system/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"google.golang.org/grpc/metadata"

"github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/version"
)

Expand All @@ -33,6 +34,12 @@ type Environment struct {
Platform string
}

type File struct {
Path string
Exist bool
ProcessIsRunning bool
}

// Info is an object that contains machine information
// Most of the code is taken from https://github.com/matishsiao/goInfo
type Info struct {
Expand All @@ -51,6 +58,7 @@ type Info struct {
SystemProductName string
SystemManufacturer string
Environment Environment
Files []File
}

// extractUserAgent extracts Netbird's agent (client) name and version from the outgoing context
Expand Down Expand Up @@ -132,3 +140,21 @@ func isDuplicated(addresses []NetworkAddress, addr NetworkAddress) bool {
}
return false
}

// GetInfoWithChecks retrieves and parses the system information with applied checks.
func GetInfoWithChecks(ctx context.Context, checks []*proto.Checks) (*Info, error) {
processCheckPaths := make([]string, 0)
for _, check := range checks {
processCheckPaths = append(processCheckPaths, check.GetFiles()...)
}

files, err := checkFileAndProcess(processCheckPaths)
if err != nil {
return nil, err
}

info := GetInfo(ctx)
info.Files = files

return info, nil
}
5 changes: 5 additions & 0 deletions client/system/info_android.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ func GetInfo(ctx context.Context) *Info {
return gio
}

// checkFileAndProcess checks if the file path exists and if a process is running at that path.
func checkFileAndProcess(paths []string) ([]File, error) {
return []File{}, nil
}

func uname() []string {
res := run("/system/bin/uname", "-a")
return strings.Split(res, " ")
Expand Down
5 changes: 5 additions & 0 deletions client/system/info_ios.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ func GetInfo(ctx context.Context) *Info {
return gio
}

// checkFileAndProcess checks if the file path exists and if a process is running at that path.
func checkFileAndProcess(paths []string) ([]File, error) {
return []File{}, nil
}

// extractOsVersion extracts operating system version from context or returns the default
func extractOsVersion(ctx context.Context, defaultName string) string {
v, ok := ctx.Value(OsVersionCtxKey).(string)
Expand Down
58 changes: 58 additions & 0 deletions client/system/process.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//go:build windows || (linux && !android) || (darwin && !ios)

package system

import (
"os"
"slices"

"github.com/shirou/gopsutil/v3/process"
)

// getRunningProcesses returns a list of running process paths.
func getRunningProcesses() ([]string, error) {
processes, err := process.Processes()
if err != nil {
return nil, err
}

processMap := make(map[string]bool)
for _, p := range processes {
path, _ := p.Exe()
if path != "" {
processMap[path] = true
}
}

uniqueProcesses := make([]string, 0, len(processMap))
for p := range processMap {
uniqueProcesses = append(uniqueProcesses, p)
}

return uniqueProcesses, nil
}

// checkFileAndProcess checks if the file path exists and if a process is running at that path.
func checkFileAndProcess(paths []string) ([]File, error) {
files := make([]File, len(paths))
if len(paths) == 0 {
return files, nil
}

runningProcesses, err := getRunningProcesses()
if err != nil {
return nil, err
}

for i, path := range paths {
file := File{Path: path}

_, err := os.Stat(path)
file.Exist = !os.IsNotExist(err)

file.ProcessIsRunning = slices.Contains(runningProcesses, path)
files[i] = file
}

return files, nil
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ require (
github.com/pion/turn/v3 v3.0.1
github.com/prometheus/client_golang v1.19.1
github.com/rs/xid v1.3.0
github.com/shirou/gopsutil/v3 v3.24.4
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/stretchr/testify v1.9.0
github.com/testcontainers/testcontainers-go v0.31.0
Expand Down Expand Up @@ -175,7 +176,6 @@ require (
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.53.0 // indirect
github.com/prometheus/procfs v0.15.0 // indirect
github.com/shirou/gopsutil/v3 v3.24.4 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
Expand Down
5 changes: 3 additions & 2 deletions management/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import (

type Client interface {
io.Closer
Sync(ctx context.Context, msgHandler func(msg *proto.SyncResponse) error) error
Sync(ctx context.Context, sysInfo *system.Info, msgHandler func(msg *proto.SyncResponse) error) error
GetServerPublicKey() (*wgtypes.Key, error)
Register(serverKey wgtypes.Key, setupKey string, jwtToken string, sysInfo *system.Info, sshKey []byte) (*proto.LoginResponse, error)
Login(serverKey wgtypes.Key, sysInfo *system.Info, sshKey []byte) (*proto.LoginResponse, error)
GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error)
GetPKCEAuthorizationFlow(serverKey wgtypes.Key) (*proto.PKCEAuthorizationFlow, error)
GetNetworkMap() (*proto.NetworkMap, error)
GetNetworkMap(sysInfo *system.Info) (*proto.NetworkMap, error)
IsHealthy() bool
SyncMeta(sysInfo *system.Info) error
}
2 changes: 1 addition & 1 deletion management/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ func TestClient_Sync(t *testing.T) {
ch := make(chan *mgmtProto.SyncResponse, 1)

go func() {
err = client.Sync(context.Background(), func(msg *mgmtProto.SyncResponse) error {
err = client.Sync(context.Background(), info, func(msg *mgmtProto.SyncResponse) error {
ch <- msg
return nil
})
Expand Down