Adds observability and fixes cross cases

- strict-kem vs strict-rp said "Connected + Quantum resistance: true" but it is actually blocked
- perm-kem vs perm-rp "Connected + Quantum resistance: true" but it's a classic WG link, without PQ safety
This commit is contained in:
riccardom
2026-09-11 15:03:47 +02:00
parent 1ee012160b
commit 91f1a724e2
9 changed files with 137 additions and 28 deletions
+4
View File
@@ -27,6 +27,7 @@ import (
"github.com/netbirdio/netbird/client/anonymize"
"github.com/netbirdio/netbird/client/configs"
"github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/client/internal/pqkem"
"github.com/netbirdio/netbird/client/internal/profilemanager"
"github.com/netbirdio/netbird/client/internal/updater/installer"
nbstatus "github.com/netbirdio/netbird/client/status"
@@ -708,6 +709,9 @@ func (g *BundleGenerator) addCommonConfigFields(configContent *strings.Builder)
configContent.WriteString(fmt.Sprintf("DisableIPv6Discovery: %v\n", g.internalConfig.DisableIPv6Discovery))
configContent.WriteString(fmt.Sprintf("RosenpassEnabled: %v\n", g.internalConfig.RosenpassEnabled))
configContent.WriteString(fmt.Sprintf("RosenpassPermissive: %v\n", g.internalConfig.RosenpassPermissive))
// ML-KEM (Rosenpass alternative) is env-driven, not part of the config, so read it here.
configContent.WriteString(fmt.Sprintf("MLKEMEnabled: %v\n", pqkem.Enabled()))
configContent.WriteString(fmt.Sprintf("MLKEMStrict: %v\n", pqkem.Strict()))
if g.internalConfig.ServerSSHAllowed != nil {
configContent.WriteString(fmt.Sprintf("ServerSSHAllowed: %v\n", *g.internalConfig.ServerSSHAllowed))
}
+7 -1
View File
@@ -1187,7 +1187,13 @@ func isRosenpassEnabled(remoteRosenpassPubKey []byte) bool {
// the status "Quantum resistance" field: either Rosenpass (the remote advertised a
// Rosenpass key) or the ML-KEM exchange (a PQ PSK has been derived for this peer).
func (conn *Conn) quantumResistant(remoteRosenpassPubKey []byte, pqEstablished bool) bool {
return isRosenpassEnabled(remoteRosenpassPubKey) || pqEstablished
// Rosenpass protects the tunnel only when both sides run it: the local peer has a
// Rosenpass key AND the remote advertised one. Checking only the remote key would
// report a plain (or blocked) tunnel as quantum-resistant when the local side does
// not run Rosenpass — e.g. against a peer that merely advertises it in a mixed
// deployment. A homogeneous Rosenpass deployment (both sides on) is unaffected.
rosenpassActive := conn.config.RosenpassConfig.PubKey != nil && isRosenpassEnabled(remoteRosenpassPubKey)
return rosenpassActive || pqEstablished
}
func evalConnStatus(in connStatusInputs) guard.ConnStatus {
+31
View File
@@ -142,6 +142,14 @@ type RosenpassState struct {
Permissive bool
}
// MLKEMState contains the latest state of the ML-KEM post-quantum exchange, the
// Rosenpass alternative. Strict is the ML-KEM counterpart of Rosenpass non-permissive:
// it fails closed until the KEM PSK is established.
type MLKEMState struct {
Enabled bool
Strict bool
}
// NSGroupState represents the status of a DNS server group, including associated domains,
// whether it's enabled, and the last error message encountered during probing.
type NSGroupState struct {
@@ -159,6 +167,7 @@ type FullStatus struct {
SignalState SignalState
LocalPeerState LocalPeerState
RosenpassState RosenpassState
MLKEMState MLKEMState
Relays []relay.ProbeResult
NSGroupStates []NSGroupState
NumOfForwardingRules int
@@ -209,6 +218,8 @@ type Status struct {
notifier *notifier
rosenpassEnabled bool
rosenpassPermissive bool
mlkemEnabled bool
mlkemStrict bool
// sessionExpiresAt is the absolute UTC instant at which the peer's SSO
// session expires. Zero when the peer is not SSO-tracked or login
// expiration is disabled. Populated from management LoginResponse /
@@ -952,6 +963,14 @@ func (d *Status) UpdateRosenpass(rosenpassEnabled, rosenpassPermissive bool) {
d.rosenpassEnabled = rosenpassEnabled
}
// UpdateMLKEM updates the ML-KEM post-quantum exchange configuration.
func (d *Status) UpdateMLKEM(mlkemEnabled, mlkemStrict bool) {
d.mux.Lock()
defer d.mux.Unlock()
d.mlkemEnabled = mlkemEnabled
d.mlkemStrict = mlkemStrict
}
func (d *Status) UpdateLazyConnection(enabled bool) {
d.mux.Lock()
defer d.mux.Unlock()
@@ -1044,6 +1063,15 @@ func (d *Status) GetRosenpassState() RosenpassState {
}
}
func (d *Status) GetMLKEMState() MLKEMState {
d.mux.RLock()
defer d.mux.RUnlock()
return MLKEMState{
d.mlkemEnabled,
d.mlkemStrict,
}
}
func (d *Status) GetLazyConnection() bool {
d.mux.RLock()
defer d.mux.RUnlock()
@@ -1186,6 +1214,7 @@ func (d *Status) GetFullStatus() FullStatus {
SignalState: d.GetSignalState(),
Relays: d.GetRelayStates(),
RosenpassState: d.GetRosenpassState(),
MLKEMState: d.GetMLKEMState(),
NSGroupStates: d.GetDNSStates(),
NumOfForwardingRules: len(d.ForwardingRules()),
LazyConnectionEnabled: d.GetLazyConnection(),
@@ -1559,6 +1588,8 @@ func (fs FullStatus) ToProto() *proto.FullStatus {
pbFullStatus.LocalPeerState.WgPort = int32(fs.LocalPeerState.WgPort)
pbFullStatus.LocalPeerState.RosenpassPermissive = fs.RosenpassState.Permissive
pbFullStatus.LocalPeerState.RosenpassEnabled = fs.RosenpassState.Enabled
pbFullStatus.LocalPeerState.MlkemEnabled = fs.MLKEMState.Enabled
pbFullStatus.LocalPeerState.MlkemStrict = fs.MLKEMState.Strict
pbFullStatus.NumberOfForwardingRules = int32(fs.NumOfForwardingRules)
pbFullStatus.LazyConnectionEnabled = fs.LazyConnectionEnabled