Windows: encrypt the sensitive config.json data
Describe the problem
Commands are heavily delayed with returning results -
Command: netbird status
Output Time: 14 seconds
Command: netbird status --ipv4
Output Time: 14 seconds
Comments: (ipconfig & netsh show the IP info instantly as the information is local)
Security Concerns -
Private Keys & SSH Keys are exposed in plain text in the profile json Windows DPAPI (locked to TrustedInstaller or service acocunt) at the very least would be recommended.
Issue relationship: https://github.com/netbirdio/netbird/issues/1798
Windows Client config and UI -
You can lock down the profile additions and settings via a config, but they still show as options in the clients UI. Should be an option to display ONLY IP Info, Connect / Disconnect in client if update settings config option is set.
Are you using NetBird Cloud?
Self Hosted ( Windows client so, makes no difference)
0.55.1
Self-Hosted
Is any other VPN software installed?
No.
If yes, which one?
Debug output
To help us resolve the problem, please attach the following anonymized status output
netbird status -dA
Have you tried these troubleshooting steps?
- [ x ] Reviewed client troubleshooting (if applicable)
- [ x ] Checked for newer NetBird versions
- [ x ] Searched for similar issues on GitHub (including closed ones)
- [ x ] Restarted the NetBird client
- [ NA] Disabled other VPN software
- [NA] Checked firewall settings
Potentially something similar to this for the encryption portion.
This was handled by grok, so, may need to be modified.
Step-by-Step Implementation for NetbirdAssuming Netbird's config is in JSON (e.g., private keys in a file like C:\ProgramData\Netbird\config.json), modify the service to encrypt sensitive fields after initial setup. Netbird is written in Go, so use Go's crypto API with Windows DPAPI calls.Identify Sensitive Data:WireGuard private keys, setup keys, auth tokens, management URLs with credentials. During initial setup (e.g., netbird up --setup-key), generate and immediately encrypt these.
Choose DPAPI Scope:Machine-Wide (Recommended for Services): Use CRYPTPROTECT_LOCAL_MACHINE flag. Decryptable by any process on the machine running as SYSTEM or with machine context access. User-Scoped: Tie to the service account (e.g., a custom low-privilege account via sc config netbird obj= "NT SERVICE\netbird"). Use if multi-user isolation is needed.
Encrypt Data in Code (Post-Setup):Use Windows API CryptProtectData via Go (e.g., via golang.org/x/sys/windows or CGO for direct P/Invoke). Example Go Snippet (simplified; integrate into Netbird's config handler):go
package main
import (
"crypto/rand"
"encoding/base64"
"fmt"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var (
advapi32 = windows.NewLazySystemDLL("advapi32.dll")
procCryptProtectData = advapi32.NewProc("CryptProtectData")
)
func encryptData(data []byte) ([]byte, error) {
var (
dataIn = &windows.DATA_BLOB{Len: uint32(len(data)), Data: &data[0]}
dataOut windows.DATA_BLOB
ent = windows.DATA_BLOB{} // Optional entropy
)
ret, _, err := procCryptProtectData.Call(
uintptr(unsafe.Pointer(dataIn)),
uintptr(unsafe.Pointer(&dataOut)),
uintptr(unsafe.Pointer(&ent)),
0, // Reserved
0, // Reserved
windows.CRYPTPROTECT_UI_FORBIDDEN|windows.CRYPTPROTECT_LOCAL_MACHINE, // Flags: Machine-wide, no UI
0, // Reserved
)
if ret == 0 {
return nil, err
}
defer windows.LocalFree(windows.Handle(dataOut.Data))
encrypted := make([]byte, dataOut.Len)
copy(encrypted, (*[1 << 30]byte)(unsafe.Pointer(dataOut.Data))[:dataOut.Len])
return encrypted, nil
}
// Usage in Netbird setup: After generating private key...
privateKey := []byte("your-wireguard-private-key")
encryptedKey, err := encryptData(privateKey)
if err != nil {
// Handle error
}
// Store base64(encryptedKey) in config.json
fmt.Println(base64.StdEncoding.EncodeToString(encryptedKey))
Flags Explanation:CRYPTPROTECT_LOCAL_MACHINE: Ensures machine-wide protection. CRYPTPROTECT_UI_FORBIDDEN: Prevents user prompts, suitable for services.
Store Encrypted Data:Save the encrypted blob (base64-encoded for JSON) in config.json or registry (e.g., HKLM\SOFTWARE\Netbird under service SID). Set file/registry ACLs: Use icacls or SetNamedSecurityInfo to restrict to NT SERVICE\netbird, SYSTEM, and Administrators (read-only for non-service).Command: icacls "C:\ProgramData\Netbird\config.json" /inheritance:r /grant "NT SERVICE\netbird":R /grant SYSTEM:F /grant Administrators:F
Avoid plaintext in logs: Use secure logging (e.g., Event Tracing for Windows).
Decrypt at Runtime:On service start, use CryptUnprotectData to decrypt only when needed (e.g., for WireGuard init). Example Go Snippet:go
var procCryptUnprotectData = advapi32.NewProc("CryptUnprotectData")
func decryptData(encrypted []byte) ([]byte, error) {
var (
dataIn windows.DATA_BLOB{Len: uint32(len(encrypted)), Data: &encrypted[0]}
dataOut windows.DATA_BLOB
)
ret, _, err := procCryptUnprotectData.Call(
uintptr(unsafe.Pointer(&dataIn)),
0, // Optional description
0, // Optional entropy
0, // Reserved
0, // Reserved
windows.CRYPTPROTECT_UI_FORBIDDEN|windows.CRYPTPROTECT_LOCAL_MACHINE,
uintptr(unsafe.Pointer(&dataOut)),
)
if ret == 0 {
return nil, err
}
defer windows.LocalFree(windows.Handle(dataOut.Data))
decrypted := make([]byte, dataOut.Len)
copy(decrypted, (*[1 << 30]byte)(unsafe.Pointer(dataOut.Data))[:dataOut.Len])
return decrypted, nil
}
Load into memory for WireGuard; never write plaintext to disk.
Post-Setup Automation:After initial netbird up, trigger encryption via a setup hook or scheduled task running as the service. For updates: Re-encrypt on config changes; use versioning to avoid lockouts.
Additional Hardening:Service Account: Run Netbird as a custom account (not Local System) with minimal privileges. Use sc config to set it, then apply DPAPI user-scope. File Protection: Combine with EFS on the config directory: cipher /e C:\ProgramData\Netbird. Auditing: Enable Object Access auditing on config files/registry via Group Policy. Monitor for unauthorized access. Integrity Checks: Use file hashes or Windows Integrity Levels to detect tampering. Backup/Rotation: Store encrypted backups off-machine (e.g., encrypted via BitLocker). Rotate DPAPI master keys periodically via cipher /u. Testing: Verify decryption only works under service context (e.g., run as other users fails).
Will there be any added security measures to obscuring the client configs or locking them down to the netbird service daemon? Clear exposure in plaint text configs doesnt scream secure.
Wanted to circle back on this and see if there is anything on the roadmap to increase security of locking down the configs. Having certain information exposed or easily changeable is convenient, but not secure by any means.
Thanks for the detailed report with a possible resultions included. I will note it down in our tracker for consideration in the future as it makes a lot of sense.
I am pretty sure the team is more closely looking into those kind of local daemon requests timeout (those seem to happen mostly on Windows), can you report it in a new Issue if you still see it after the next 1-2 releases?
PS: I'll make this Issue about the security improvement idea.
@nazarewk The status timeout issue was in fact addressed. I closed out another issue I had reported for just that. I tested the latest client version (which was rather easy since I have never been able to get STUN working) and it did complete rather quickly. So, the only part is enhancing the security around the windows config. Since alot of the stuff is controllable already via flags with the netbird cli daemon, might as well let it be the only thing that can see the config information. Better protection that way.
@PowershellScripter thanks for the request. We've plans to improve security around the config file in Q1 next year.
@mlsmaycon Awesome! My company is still going to move to NetBird before then as we have our computers locked down, but it would definitely be nice to have certain info hidden from plain text view. This way certain information cant be viewed by the user. Or better, simplifying the config. More so than anything, the cli switches need heavy cleanup since there is too much mixed information between config files and deprecated cli switches. I already submitted an issue in relation to that though.