Skip to content

Commit e0e0d23

Browse files
committed
more tweaks
1 parent 1b8c051 commit e0e0d23

File tree

7 files changed

+111
-52
lines changed

7 files changed

+111
-52
lines changed

README.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,26 @@ The MDM for startups that actually care about security.
66

77
Your startup just hit the enterprise sales milestone where someone asks "are you SOC 2 compliant?" Meanwhile, your engineering team runs OpenBSD on ThinkPads, Arch on Frameworks, and that one person still dailying Plan 9.
88

9-
Traditional MDMs want root access, auto-update themselves from the internet, and can execute arbitrary code pushed from their cloud. Your security engineer just had an aneurysm.
9+
Traditional MDMs run as root, execute arbitrary code from their cloud servers, and auto-install binaries downloaded from the internet. Your security engineer just had an aneurysm.
1010

1111
## Our Solution
1212

1313
gitMDM proves compliance without the backdoor:
1414

1515
```
16-
Traditional MDM: "Install our kernel extension!"
16+
Traditional MDM: "Install our root agent that downloads and executes code from our servers!"
1717
Your Team: "How about no."
1818
19-
gitMDM: "Run a read-only agent that reports to YOUR server"
19+
gitMDM: "Run a read-only agent as a regular user that only reports"
2020
Your Team: "...continue"
2121
```
2222

2323
### Why Your Security Team Will Actually Approve This
2424

25-
- **Zero Remote Execution**: Can't push commands. Not won't. Can't. The server only receives data.
26-
- **No Auto-Updates**: Agent is a static binary. Updates require YOU to rebuild and redeploy.
27-
- **Runs as User**: No root, no SYSTEM. Just a regular user process.
28-
- **You Own Everything**: Your server, your git repo, your data. Host it in your VPC.
25+
- **Zero Remote Execution**: Can't push commands or install software. The server only receives data.
26+
- **No Auto-Updates**: No downloading binaries from the internet. Updates require YOU to rebuild and redeploy.
27+
- **Runs as User**: No root, no SYSTEM. Can't execute arbitrary code or modify your system.
28+
- **You Own Everything**: Your server, your git repo, your data. No third-party cloud with root access to your fleet.
2929
- **Audit Everything**: Every change is a git commit. `git blame` for compliance.
3030

3131
## Quick Start for the Impatient
@@ -51,10 +51,10 @@ Join keys stored in `~/.config/gitmdm/` (or wherever your OS says), not in proce
5151

5252
| SOC 2 Says | Traditional MDMs Do | We Do |
5353
|------------|---------------------|--------|
54-
| Disk encryption | Run as root, phone home for instructions | Read `/proc/mounts` as user |
55-
| Screen locks | Auto-update from vendor's CDN | Check your screensaver config |
56-
| OS updates | Force reboots during demos | Report version numbers |
57-
| Firewall enabled | Execute arbitrary scripts from cloud | Check `iptables -L` output |
54+
| Disk encryption | Run as root to verify and enforce | Check encryption status as user |
55+
| Screen locks | Execute scripts as root to enforce policies | Read your existing screensaver config |
56+
| OS updates | Download and install updates as root | Report current version numbers |
57+
| Firewall enabled | Execute commands as root to modify rules | Check firewall status (read-only) |
5858

5959
## Platform Detection That Actually Works
6060

@@ -129,7 +129,7 @@ A: No. Check the code. The handler doesn't exist.
129129
A: It's 2 dependencies: yaml and retry. Vendor them if paranoid.
130130

131131
**Q: Does it require root?**
132-
A: Never. User-level only. Your kernel remains unmolested.
132+
A: Never. User-level only. Just reads system configuration.
133133

134134
**Q: What data does it collect?**
135135
A: Read `checks.yaml`. It's compiled in. No surprises.
@@ -154,4 +154,4 @@ A: On the roadmap. PRs welcome from fellow paranoids.
154154

155155
---
156156

157-
*"Because your security posture shouldn't require the missionary position."*
157+
*"Because compliance doesn't require compromise."*

cmd/agent/checks.yaml

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -576,14 +576,8 @@ checks:
576576
- output: sw_vers
577577
linux:
578578
- file: /etc/os-release
579-
freebsd:
580-
- output: freebsd-version
581-
openbsd:
582-
- output: sysctl -n kern.version
583-
netbsd:
584-
- output: sysctl -n kern.version
585-
dragonfly:
586-
- output: sysctl -n kern.version
579+
unix:
580+
- output: uname -srm
587581
solaris:
588582
- file: /etc/release
589583
illumos:

cmd/server/main.go

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,10 @@ var (
8181

8282
// ComplianceCache stores pre-calculated compliance stats for a device.
8383
type ComplianceCache struct {
84-
PassCount int
85-
FailCount int
86-
NACount int
84+
PassCount int
85+
FailCount int
86+
NACount int
87+
HasCheckedIn bool // true if device has reported since server startup
8788
}
8889

8990
// Server represents the gitMDM server that receives and stores compliance reports.
@@ -391,14 +392,26 @@ func (s *Server) loadDevices(ctx context.Context) error {
391392
// updateComplianceCacheLocked updates the compliance cache for a device.
392393
// Caller must hold s.mu lock.
393394
func (s *Server) updateComplianceCacheLocked(device *gitmdm.Device) {
395+
// Get existing cache to preserve HasCheckedIn flag
396+
existingCache, exists := s.complianceCache[device.HardwareID]
394397
cache := &ComplianceCache{}
398+
if exists {
399+
cache.HasCheckedIn = existingCache.HasCheckedIn
400+
}
395401

396-
// Filter out checks that are more than 1 hour older than LastSeen
397-
staleThreshold := device.LastSeen.Add(-1 * time.Hour)
398-
402+
// Determine staleness threshold
403+
var staleThreshold time.Time
404+
if cache.HasCheckedIn {
405+
// Device has checked in since server startup - use 1 day staleness
406+
staleThreshold = time.Now().Add(-24 * time.Hour)
407+
} else {
408+
// Device hasn't checked in yet - use all data from git
409+
staleThreshold = time.Time{} // Zero time means no filtering
410+
}
411+
399412
for _, check := range device.Checks {
400-
// Skip stale checks that are too old
401-
if !check.Timestamp.IsZero() && check.Timestamp.Before(staleThreshold) {
413+
// Skip stale checks only if we have a threshold and the check has a timestamp
414+
if !staleThreshold.IsZero() && !check.Timestamp.IsZero() && check.Timestamp.Before(staleThreshold) {
402415
continue
403416
}
404417

@@ -526,7 +539,9 @@ func (s *Server) handleIndex(writer http.ResponseWriter, req *http.Request) {
526539
item.ComplianceClass = "poor"
527540
}
528541
} else {
529-
item.ComplianceClass = "poor"
542+
// No checks data available
543+
item.ComplianceClass = "unknown"
544+
item.ComplianceEmoji = "—"
530545
}
531546
} else {
532547
item.ComplianceClass = "poor"
@@ -633,6 +648,7 @@ func (s *Server) handleDevice(writer http.ResponseWriter, r *http.Request) {
633648

634649
s.mu.RLock()
635650
device, exists := s.devices[hardwareID]
651+
cache, _ := s.complianceCache[hardwareID]
636652
s.mu.RUnlock()
637653

638654
if !exists {
@@ -641,8 +657,18 @@ func (s *Server) handleDevice(writer http.ResponseWriter, r *http.Request) {
641657
return
642658
}
643659

660+
// Determine staleness threshold based on whether device has checked in
661+
var staleThreshold time.Time
662+
if cache != nil && cache.HasCheckedIn {
663+
// Device has checked in since server startup - use 1 day staleness
664+
staleThreshold = time.Now().Add(-24 * time.Hour)
665+
} else {
666+
// Device hasn't checked in yet - use all data from git
667+
staleThreshold = time.Time{} // Zero time means no filtering
668+
}
669+
644670
// Build detailed view model with compliance analysis
645-
viewData := viewmodels.BuildDeviceDetail(device)
671+
viewData := viewmodels.BuildDeviceDetail(device, staleThreshold)
646672

647673
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
648674
if err := s.tmpl.ExecuteTemplate(writer, "device.html", viewData); err != nil {
@@ -783,6 +809,12 @@ func (s *Server) handleReport(writer http.ResponseWriter, request *http.Request)
783809
// Always update in-memory cache first for immediate availability
784810
s.mu.Lock()
785811
s.devices[device.HardwareID] = device
812+
// Mark that this device has checked in since server startup
813+
if existingCache, exists := s.complianceCache[device.HardwareID]; exists {
814+
existingCache.HasCheckedIn = true
815+
} else {
816+
s.complianceCache[device.HardwareID] = &ComplianceCache{HasCheckedIn: true}
817+
}
786818
// Update compliance cache
787819
s.updateComplianceCacheLocked(device)
788820
s.mu.Unlock()

cmd/server/templates/device.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,15 @@ <h3 class="empty-state-title">No Compliance Data Available</h3>
619619
{{end}}
620620
</div>
621621

622+
<div style="margin-top: 48px; padding-top: 24px; border-top: 1px solid var(--color-border-default); text-align: center;">
623+
<a href="https://github.com/codeGROOVE-dev/gitMDM" target="_blank" style="color: var(--color-fg-muted); text-decoration: none; font-size: 12px;">
624+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style="vertical-align: middle; margin-right: 4px;">
625+
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
626+
</svg>
627+
Powered by gitMDM
628+
</a>
629+
</div>
630+
622631
<script src="/static/device.js"></script>
623632
</body>
624633
</html>

cmd/server/templates/index.html

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ <h1 class="page-title">gitMDM Dashboard</h1>
459459
</div>
460460
<div class="stat-card">
461461
<div class="stat-value neutral">{{$checkingCount}}</div>
462-
<div class="stat-label">Checking</div>
462+
<div class="stat-label">No Data</div>
463463
</div>
464464
</div>
465465

@@ -490,7 +490,7 @@ <h1 class="page-title">gitMDM Dashboard</h1>
490490
Has Issues
491491
</button>
492492
<button class="filter-tab {{if eq .Status "checking"}}active{{end}}" data-status="checking">
493-
Checking
493+
No Data
494494
</button>
495495
</div>
496496
</div>
@@ -541,7 +541,7 @@ <h1 class="page-title">gitMDM Dashboard</h1>
541541
<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor">
542542
<path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm9.78-2.22-5.5 5.5-1.78-1.78-.72.72 2.5 2.5 6.22-6.22-.72-.72Z"/>
543543
</svg>
544-
Checking
544+
No Data
545545
</span>
546546
{{end}}
547547
</td>
@@ -600,6 +600,15 @@ <h2 class="empty-state-title">∅ Devices</h2>
600600
{{end}}
601601
</div>
602602

603+
<div style="margin-top: 48px; padding-top: 24px; border-top: 1px solid var(--color-border-default); text-align: center;">
604+
<a href="https://github.com/codeGROOVE-dev/gitMDM" target="_blank" style="color: var(--color-fg-muted); text-decoration: none; font-size: 12px;">
605+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style="vertical-align: middle; margin-right: 4px;">
606+
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
607+
</svg>
608+
Powered by gitMDM
609+
</a>
610+
</div>
611+
603612
<script src="/static/index.js"></script>
604613
</body>
605614
</html>

cmd/server/templates/index_complex.html

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ <h3><a href="/device/{{.HardwareID}}">{{.Hostname}}</a></h3>
319319
{{if .ComplianceScore}}
320320
{{if ge .ComplianceScore 0.9}}Excellent{{else if ge .ComplianceScore 0.7}}Good{{else}}Needs Attention{{end}}
321321
{{else}}
322-
Checking...
322+
No Data
323323
{{end}}
324324
</div>
325325
</div>
@@ -353,5 +353,14 @@ <h3>No Devices Found</h3>
353353
{{end}}
354354
</div>
355355
</div>
356+
357+
<div style="margin-top: 48px; padding-top: 24px; border-top: 1px solid #dee2e6; text-align: center;">
358+
<a href="https://github.com/codeGROOVE-dev/gitMDM" target="_blank" style="color: #6c757d; text-decoration: none; font-size: 14px;">
359+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style="vertical-align: middle; margin-right: 4px;">
360+
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
361+
</svg>
362+
Powered by gitMDM
363+
</a>
364+
</div>
356365
</body>
357366
</html>

internal/viewmodels/viewmodels.go

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -67,19 +67,17 @@ type CheckResult struct {
6767
}
6868

6969
// BuildDeviceDetail creates a detailed view model for a single device.
70-
func BuildDeviceDetail(device *gitmdm.Device) *DeviceDetail {
70+
// staleThreshold determines which checks to include - zero time means include all.
71+
func BuildDeviceDetail(device *gitmdm.Device, staleThreshold time.Time) *DeviceDetail {
7172
detail := &DeviceDetail{
7273
Device: device,
7374
CheckResults: make(map[string]CheckResult),
7475
}
7576

76-
// Filter out checks that are more than 1 hour older than LastSeen
77-
staleThreshold := device.LastSeen.Add(-1 * time.Hour)
78-
7977
// Process all checks
8078
for checkName, check := range device.Checks {
81-
// Skip stale checks that are too old
82-
if !check.Timestamp.IsZero() && check.Timestamp.Before(staleThreshold) {
79+
// Skip stale checks only if we have a threshold and the check has a timestamp
80+
if !staleThreshold.IsZero() && !check.Timestamp.IsZero() && check.Timestamp.Before(staleThreshold) {
8381
continue
8482
}
8583
var emoji string
@@ -212,7 +210,7 @@ func extractFromSystemInfo(device *gitmdm.Device) (osName, version string) {
212210
if osName, osVersion := parseLinuxOutput(output); osName != unknownVersion {
213211
return osName, osVersion
214212
}
215-
if osName, osVersion := parseFreeBSDOutput(output); osName != unknownVersion {
213+
if osName, osVersion := parseUnixUnameOutput(output); osName != unknownVersion {
216214
return osName, osVersion
217215
}
218216
if osName, osVersion := parseWindowsOutput(output); osName != unknownVersion {
@@ -309,20 +307,28 @@ func parseLinuxOutput(output string) (osName, version string) {
309307
return unknownVersion, unknownVersion
310308
}
311309

312-
// parseFreeBSDOutput parses FreeBSD freebsd-version output.
313-
func parseFreeBSDOutput(output string) (osName, version string) {
314-
if !strings.Contains(output, "FreeBSD") {
310+
// parseUnixUnameOutput parses uname -srm output for Unix systems.
311+
// Example outputs:
312+
// OpenBSD 7.4 amd64
313+
// FreeBSD 14.0-RELEASE amd64
314+
// NetBSD 10.0 amd64
315+
// DragonFly 6.4-RELEASE x86_64.
316+
func parseUnixUnameOutput(output string) (osName, version string) {
317+
parts := strings.Fields(output)
318+
if len(parts) < 2 {
315319
return unknownVersion, unknownVersion
316320
}
317321

318-
lines := strings.Split(output, "\n")
319-
if len(lines) > 0 {
320-
parts := strings.Fields(lines[0])
321-
if len(parts) >= 2 {
322-
return "FreeBSD", parts[1]
323-
}
322+
// First field is OS name, second is version
323+
osName = parts[0]
324+
version = parts[1]
325+
326+
// Clean up version strings that have -RELEASE, -CURRENT, etc.
327+
if idx := strings.Index(version, "-"); idx > 0 {
328+
version = version[:idx]
324329
}
325-
return "FreeBSD", unknownVersion
330+
331+
return osName, version
326332
}
327333

328334
// parseWindowsOutput parses Windows systeminfo output.

0 commit comments

Comments
 (0)