add disk encryption check

This commit is contained in:
mlsmaycon
2026-01-17 19:56:50 +01:00
parent 245481f33b
commit 279e96e6b1
23 changed files with 1637 additions and 760 deletions

View File

@@ -95,6 +95,27 @@ type File struct {
ProcessIsRunning bool
}
// DiskEncryptionVolume represents encryption status of a volume.
type DiskEncryptionVolume struct {
Path string
Encrypted bool
}
// DiskEncryptionInfo holds encryption info for all volumes.
type DiskEncryptionInfo struct {
Volumes []DiskEncryptionVolume `gorm:"serializer:json"`
}
// IsEncrypted returns true if the volume at path is encrypted.
func (d DiskEncryptionInfo) IsEncrypted(path string) bool {
for _, v := range d.Volumes {
if v.Path == path {
return v.Encrypted
}
}
return false
}
// Flags defines a set of options to control feature behavior
type Flags struct {
RosenpassEnabled bool
@@ -127,9 +148,10 @@ type PeerSystemMeta struct { //nolint:revive
SystemSerialNumber string
SystemProductName string
SystemManufacturer string
Environment Environment `gorm:"serializer:json"`
Flags Flags `gorm:"serializer:json"`
Files []File `gorm:"serializer:json"`
Environment Environment `gorm:"serializer:json"`
Flags Flags `gorm:"serializer:json"`
Files []File `gorm:"serializer:json"`
DiskEncryption DiskEncryptionInfo `gorm:"serializer:json"`
}
func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool {
@@ -159,6 +181,19 @@ func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool {
return false
}
sort.Slice(p.DiskEncryption.Volumes, func(i, j int) bool {
return p.DiskEncryption.Volumes[i].Path < p.DiskEncryption.Volumes[j].Path
})
sort.Slice(other.DiskEncryption.Volumes, func(i, j int) bool {
return other.DiskEncryption.Volumes[i].Path < other.DiskEncryption.Volumes[j].Path
})
equalDiskEncryption := slices.EqualFunc(p.DiskEncryption.Volumes, other.DiskEncryption.Volumes, func(vol DiskEncryptionVolume, oVol DiskEncryptionVolume) bool {
return vol.Path == oVol.Path && vol.Encrypted == oVol.Encrypted
})
if !equalDiskEncryption {
return false
}
return p.Hostname == other.Hostname &&
p.GoOS == other.GoOS &&
p.Kernel == other.Kernel &&

View File

@@ -18,6 +18,7 @@ const (
GeoLocationCheckName = "GeoLocationCheck"
PeerNetworkRangeCheckName = "PeerNetworkRangeCheck"
ProcessCheckName = "ProcessCheck"
DiskEncryptionCheckName = "DiskEncryptionCheck"
CheckActionAllow string = "allow"
CheckActionDeny string = "deny"
@@ -58,6 +59,7 @@ type ChecksDefinition struct {
GeoLocationCheck *GeoLocationCheck `json:",omitempty"`
PeerNetworkRangeCheck *PeerNetworkRangeCheck `json:",omitempty"`
ProcessCheck *ProcessCheck `json:",omitempty"`
DiskEncryptionCheck *DiskEncryptionCheck `json:",omitempty"`
}
// Copy returns a copy of a checks definition.
@@ -110,6 +112,13 @@ func (cd ChecksDefinition) Copy() ChecksDefinition {
}
copy(cdCopy.ProcessCheck.Processes, processCheck.Processes)
}
if cd.DiskEncryptionCheck != nil {
cdCopy.DiskEncryptionCheck = &DiskEncryptionCheck{
LinuxPath: cd.DiskEncryptionCheck.LinuxPath,
DarwinPath: cd.DiskEncryptionCheck.DarwinPath,
WindowsPath: cd.DiskEncryptionCheck.WindowsPath,
}
}
return cdCopy
}
@@ -153,6 +162,9 @@ func (pc *Checks) GetChecks() []Check {
if pc.Checks.ProcessCheck != nil {
checks = append(checks, pc.Checks.ProcessCheck)
}
if pc.Checks.DiskEncryptionCheck != nil {
checks = append(checks, pc.Checks.DiskEncryptionCheck)
}
return checks
}
@@ -208,6 +220,10 @@ func buildPostureCheck(postureChecksID string, name string, description string,
postureChecks.Checks.ProcessCheck = toProcessCheck(processCheck)
}
if diskEncryptionCheck := checks.DiskEncryptionCheck; diskEncryptionCheck != nil {
postureChecks.Checks.DiskEncryptionCheck = toDiskEncryptionCheck(diskEncryptionCheck)
}
return &postureChecks, nil
}
@@ -242,6 +258,10 @@ func (pc *Checks) ToAPIResponse() *api.PostureCheck {
checks.ProcessCheck = toProcessCheckResponse(pc.Checks.ProcessCheck)
}
if pc.Checks.DiskEncryptionCheck != nil {
checks.DiskEncryptionCheck = toDiskEncryptionCheckResponse(pc.Checks.DiskEncryptionCheck)
}
return &api.PostureCheck{
Id: pc.ID,
Name: pc.Name,
@@ -386,3 +406,25 @@ func toProcessCheck(check *api.ProcessCheck) *ProcessCheck {
Processes: processes,
}
}
func toDiskEncryptionCheck(check *api.DiskEncryptionCheck) *DiskEncryptionCheck {
d := &DiskEncryptionCheck{}
if check.LinuxPath != nil {
d.LinuxPath = *check.LinuxPath
}
if check.DarwinPath != nil {
d.DarwinPath = *check.DarwinPath
}
if check.WindowsPath != nil {
d.WindowsPath = *check.WindowsPath
}
return d
}
func toDiskEncryptionCheckResponse(check *DiskEncryptionCheck) *api.DiskEncryptionCheck {
return &api.DiskEncryptionCheck{
LinuxPath: &check.LinuxPath,
DarwinPath: &check.DarwinPath,
WindowsPath: &check.WindowsPath,
}
}

View File

@@ -0,0 +1,52 @@
package posture
import (
"context"
"fmt"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
)
// DiskEncryptionCheck verifies that specified volumes are encrypted.
type DiskEncryptionCheck struct {
LinuxPath string
DarwinPath string
WindowsPath string
}
var _ Check = (*DiskEncryptionCheck)(nil)
// Name returns the name of the check.
func (d *DiskEncryptionCheck) Name() string {
return DiskEncryptionCheckName
}
// Check performs the disk encryption verification for the given peer.
func (d *DiskEncryptionCheck) Check(_ context.Context, peer nbpeer.Peer) (bool, error) {
var pathToCheck string
switch peer.Meta.GoOS {
case "linux":
pathToCheck = d.LinuxPath
case "darwin":
pathToCheck = d.DarwinPath
case "windows":
pathToCheck = d.WindowsPath
default:
return false, nil
}
if pathToCheck == "" {
return true, nil
}
return peer.Meta.DiskEncryption.IsEncrypted(pathToCheck), nil
}
// Validate checks the configuration of the disk encryption check.
func (d *DiskEncryptionCheck) Validate() error {
if d.LinuxPath == "" && d.DarwinPath == "" && d.WindowsPath == "" {
return fmt.Errorf("%s at least one path must be configured", d.Name())
}
return nil
}

View File

@@ -0,0 +1,306 @@
package posture
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/netbirdio/netbird/management/server/peer"
)
func TestDiskEncryptionCheck_Check(t *testing.T) {
tests := []struct {
name string
input peer.Peer
check DiskEncryptionCheck
wantErr bool
isValid bool
}{
{
name: "linux with encrypted root",
input: peer.Peer{
Meta: peer.PeerSystemMeta{
GoOS: "linux",
DiskEncryption: peer.DiskEncryptionInfo{
Volumes: []peer.DiskEncryptionVolume{
{Path: "/", Encrypted: true},
{Path: "/home", Encrypted: true},
},
},
},
},
check: DiskEncryptionCheck{
LinuxPath: "/",
},
wantErr: false,
isValid: true,
},
{
name: "linux with unencrypted root",
input: peer.Peer{
Meta: peer.PeerSystemMeta{
GoOS: "linux",
DiskEncryption: peer.DiskEncryptionInfo{
Volumes: []peer.DiskEncryptionVolume{
{Path: "/", Encrypted: false},
{Path: "/home", Encrypted: true},
},
},
},
},
check: DiskEncryptionCheck{
LinuxPath: "/",
},
wantErr: false,
isValid: false,
},
{
name: "linux with no volume info",
input: peer.Peer{
Meta: peer.PeerSystemMeta{
GoOS: "linux",
DiskEncryption: peer.DiskEncryptionInfo{},
},
},
check: DiskEncryptionCheck{
LinuxPath: "/",
},
wantErr: false,
isValid: false,
},
{
name: "darwin with encrypted root",
input: peer.Peer{
Meta: peer.PeerSystemMeta{
GoOS: "darwin",
DiskEncryption: peer.DiskEncryptionInfo{
Volumes: []peer.DiskEncryptionVolume{
{Path: "/", Encrypted: true},
},
},
},
},
check: DiskEncryptionCheck{
DarwinPath: "/",
},
wantErr: false,
isValid: true,
},
{
name: "darwin with unencrypted root",
input: peer.Peer{
Meta: peer.PeerSystemMeta{
GoOS: "darwin",
DiskEncryption: peer.DiskEncryptionInfo{
Volumes: []peer.DiskEncryptionVolume{
{Path: "/", Encrypted: false},
},
},
},
},
check: DiskEncryptionCheck{
DarwinPath: "/",
},
wantErr: false,
isValid: false,
},
{
name: "windows with encrypted C drive",
input: peer.Peer{
Meta: peer.PeerSystemMeta{
GoOS: "windows",
DiskEncryption: peer.DiskEncryptionInfo{
Volumes: []peer.DiskEncryptionVolume{
{Path: "C:", Encrypted: true},
{Path: "D:", Encrypted: false},
},
},
},
},
check: DiskEncryptionCheck{
WindowsPath: "C:",
},
wantErr: false,
isValid: true,
},
{
name: "windows with unencrypted C drive",
input: peer.Peer{
Meta: peer.PeerSystemMeta{
GoOS: "windows",
DiskEncryption: peer.DiskEncryptionInfo{
Volumes: []peer.DiskEncryptionVolume{
{Path: "C:", Encrypted: false},
{Path: "D:", Encrypted: true},
},
},
},
},
check: DiskEncryptionCheck{
WindowsPath: "C:",
},
wantErr: false,
isValid: false,
},
{
name: "unsupported ios operating system",
input: peer.Peer{
Meta: peer.PeerSystemMeta{
GoOS: "ios",
},
},
check: DiskEncryptionCheck{
LinuxPath: "/",
DarwinPath: "/",
WindowsPath: "C:",
},
wantErr: false,
isValid: false,
},
{
name: "unsupported android operating system",
input: peer.Peer{
Meta: peer.PeerSystemMeta{
GoOS: "android",
},
},
check: DiskEncryptionCheck{
LinuxPath: "/",
DarwinPath: "/",
WindowsPath: "C:",
},
wantErr: false,
isValid: false,
},
{
name: "linux peer with no linux path configured passes",
input: peer.Peer{
Meta: peer.PeerSystemMeta{
GoOS: "linux",
DiskEncryption: peer.DiskEncryptionInfo{
Volumes: []peer.DiskEncryptionVolume{
{Path: "/", Encrypted: false},
},
},
},
},
check: DiskEncryptionCheck{
DarwinPath: "/",
WindowsPath: "C:",
},
wantErr: false,
isValid: true,
},
{
name: "darwin peer with no darwin path configured passes",
input: peer.Peer{
Meta: peer.PeerSystemMeta{
GoOS: "darwin",
DiskEncryption: peer.DiskEncryptionInfo{
Volumes: []peer.DiskEncryptionVolume{
{Path: "/", Encrypted: false},
},
},
},
},
check: DiskEncryptionCheck{
LinuxPath: "/",
WindowsPath: "C:",
},
wantErr: false,
isValid: true,
},
{
name: "windows peer with no windows path configured passes",
input: peer.Peer{
Meta: peer.PeerSystemMeta{
GoOS: "windows",
DiskEncryption: peer.DiskEncryptionInfo{
Volumes: []peer.DiskEncryptionVolume{
{Path: "C:", Encrypted: false},
},
},
},
},
check: DiskEncryptionCheck{
LinuxPath: "/",
DarwinPath: "/",
},
wantErr: false,
isValid: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
isValid, err := tt.check.Check(context.Background(), tt.input)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.isValid, isValid)
})
}
}
func TestDiskEncryptionCheck_Validate(t *testing.T) {
testCases := []struct {
name string
check DiskEncryptionCheck
expectedError bool
}{
{
name: "valid linux, darwin and windows paths",
check: DiskEncryptionCheck{
LinuxPath: "/",
DarwinPath: "/",
WindowsPath: "C:",
},
expectedError: false,
},
{
name: "valid linux path only",
check: DiskEncryptionCheck{
LinuxPath: "/",
},
expectedError: false,
},
{
name: "valid darwin path only",
check: DiskEncryptionCheck{
DarwinPath: "/",
},
expectedError: false,
},
{
name: "valid windows path only",
check: DiskEncryptionCheck{
WindowsPath: "C:",
},
expectedError: false,
},
{
name: "invalid empty paths",
check: DiskEncryptionCheck{},
expectedError: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := tc.check.Validate()
if tc.expectedError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestDiskEncryptionCheck_Name(t *testing.T) {
check := DiskEncryptionCheck{}
assert.Equal(t, DiskEncryptionCheckName, check.Name())
}