Skip to content

Commit

Permalink
Integrate Rosenpass (#1153)
Browse files Browse the repository at this point in the history
This PR aims to integrate Rosenpass with NetBird. It adds a manager for Rosenpass that starts a Rosenpass server and handles the managed peers. It uses the cunicu/go-rosenpass implementation. Rosenpass will then negotiate a pre-shared key every 2 minutes and apply it to the wireguard connection.

The Feature can be enabled by setting a flag during the netbird up --enable-rosenpass command.

If two peers are both support and have the Rosenpass feature enabled they will create a post-quantum secure connection. If one of the peers or both don't have this feature enabled or are running an older version that does not have this feature yet, the NetBird client will fall back to a plain Wireguard connection without pre-shared keys for those connections (keeping Rosenpass negotiation for the rest).

Additionally, this PR includes an update of all Github Actions workflows to use go version 1.21.0 as this is a requirement for the integration.

---------

Co-authored-by: braginini <[email protected]>
Co-authored-by: Maycon Santos <[email protected]>
  • Loading branch information
3 people authored Jan 8, 2024
1 parent aa3b79d commit 5de4acf
Show file tree
Hide file tree
Showing 25 changed files with 884 additions and 530 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/android-build-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: "1.20.x"
go-version: "1.21.x"
- name: Setup Android SDK
uses: android-actions/setup-android@v2
- name: NDK Cache
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/golang-test-darwin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: "1.20.x"
go-version: "1.21.x"
- name: Checkout code
uses: actions/checkout@v3

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/golang-test-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: "1.20.x"
go-version: "1.21.x"


- name: Cache Go modules
Expand Down Expand Up @@ -50,7 +50,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: "1.20.x"
go-version: "1.21.x"

- name: Cache Go modules
uses: actions/cache@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/golang-test-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
uses: actions/setup-go@v4
id: go
with:
go-version: "1.20.x"
go-version: "1.21.x"

- name: Download wintun
uses: carlosperate/download-file-action@v2
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: golangci-lint
on: [pull_request]

permissions:
permissions:
contents: read
pull-requests: read

Expand Down Expand Up @@ -36,7 +36,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: "1.20.x"
go-version: "1.21.x"
cache: false
- name: Install dependencies
if: matrix.os == 'ubuntu-latest'
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.20"
go-version: "1.21"
cache: false
-
name: Cache Go modules
Expand Down Expand Up @@ -120,7 +120,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.20"
go-version: "1.21"
cache: false
- name: Cache Go modules
uses: actions/cache@v3
Expand Down Expand Up @@ -175,7 +175,7 @@ jobs:
name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.20"
go-version: "1.21"
cache: false
-
name: Cache Go modules
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-infrastructure-files.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: "1.20.x"
go-version: "1.21.x"

- name: Cache Go modules
uses: actions/cache@v3
Expand Down
7 changes: 5 additions & 2 deletions client/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ import (
)

const (
externalIPMapFlag = "external-ip-map"
externalIPMapFlag = "external-ip-map"
dnsResolverAddress = "dns-resolver-address"
enableRosenpassFlag = "enable-rosenpass"
preSharedKeyFlag = "preshared-key"
dnsResolverAddress = "dns-resolver-address"
)

var (
Expand All @@ -50,6 +51,7 @@ var (
preSharedKey string
natExternalIPs []string
customDNSAddress string
rosenpassEnabled bool
rootCmd = &cobra.Command{
Use: "netbird",
Short: "",
Expand Down Expand Up @@ -119,6 +121,7 @@ func init() {
`An empty string "" clears the previous configuration. `+
`E.g. --dns-resolver-address 127.0.0.1:5053 or --dns-resolver-address ""`,
)
upCmd.PersistentFlags().BoolVar(&rosenpassEnabled, enableRosenpassFlag, false, "[Experimental] Enable Rosenpass feature. If enabled, the connection will be post-quantum secured via Rosenpass.")
}

// SetupCloseHandler handles SIGTERM signal and exits with success
Expand Down
8 changes: 8 additions & 0 deletions client/cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command) error {
CustomDNSAddress: customDNSAddressConverted,
}

if rootCmd.PersistentFlags().Changed(enableRosenpassFlag) {
ic.RosenpassEnabled = &rosenpassEnabled
}

if rootCmd.PersistentFlags().Changed(preSharedKeyFlag) {
ic.PreSharedKey = &preSharedKey
}
Expand Down Expand Up @@ -153,6 +157,10 @@ func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error {
Hostname: hostName,
}

if cmd.Flag(enableRosenpassFlag).Changed {
loginRequest.RosenpassEnabled = &rosenpassEnabled
}

var loginErr error

var loginResp *proto.LoginResponse
Expand Down
11 changes: 11 additions & 0 deletions client/internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type ConfigInput struct {
PreSharedKey *string
NATExternalIPs []string
CustomDNSAddress []byte
RosenpassEnabled *bool
}

// Config Configuration type
Expand All @@ -54,6 +55,7 @@ type Config struct {
WgPort int
IFaceBlackList []string
DisableIPv6Discovery bool
RosenpassEnabled bool
// SSHKey is a private SSH key in a PEM format
SSHKey string

Expand Down Expand Up @@ -169,6 +171,10 @@ func createNewConfig(input ConfigInput) (*Config, error) {
config.PreSharedKey = *input.PreSharedKey
}

if input.RosenpassEnabled != nil {
config.RosenpassEnabled = *input.RosenpassEnabled
}

defaultAdminURL, err := parseURL("Admin URL", DefaultAdminURL)
if err != nil {
return nil, err
Expand Down Expand Up @@ -247,6 +253,11 @@ func update(input ConfigInput) (*Config, error) {
refresh = true
}

if input.RosenpassEnabled != nil {
config.RosenpassEnabled = *input.RosenpassEnabled
refresh = true
}

if refresh {
// since we have new management URL, we need to update config file
if err := util.WriteJson(input.ConfigPath, config); err != nil {
Expand Down
1 change: 1 addition & 0 deletions client/internal/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.Pe
SSHKey: []byte(config.SSHKey),
NATExternalIPs: config.NATExternalIPs,
CustomDNSAddress: config.CustomDNSAddress,
RosenpassEnabled: config.RosenpassEnabled,
}

if config.PreSharedKey != "" {
Expand Down
95 changes: 88 additions & 7 deletions client/internal/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/netbirdio/netbird/client/internal/acl"
"github.com/netbirdio/netbird/client/internal/dns"
"github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/client/internal/rosenpass"
"github.com/netbirdio/netbird/client/internal/routemanager"
"github.com/netbirdio/netbird/client/internal/wgproxy"
nbssh "github.com/netbirdio/netbird/client/ssh"
Expand Down Expand Up @@ -76,6 +77,8 @@ type EngineConfig struct {
NATExternalIPs []string

CustomDNSAddress string

RosenpassEnabled bool
}

// Engine is a mechanism responsible for reacting on Signal and Management stream events and managing connections to the remote peers.
Expand All @@ -86,6 +89,8 @@ type Engine struct {
mgmClient mgm.Client
// peerConns is a map that holds all the peers that are known to this peer
peerConns map[string]*peer.Conn
// rpManager is a Rosenpass manager
rpManager *rosenpass.Manager

// syncMsgMux is used to guarantee sequential Management Service message processing
syncMsgMux *sync.Mutex
Expand Down Expand Up @@ -185,6 +190,18 @@ func (e *Engine) Start() error {
}
e.wgInterface = wgIface

if e.config.RosenpassEnabled {
log.Infof("rosenpass is enabled")
e.rpManager, err = rosenpass.NewManager(e.config.PreSharedKey, e.config.WgIfaceName)
if err != nil {
return err
}
err := e.rpManager.Run()
if err != nil {
return err
}
}

initialRoutes, dnsServer, err := e.newDnsServer()
if err != nil {
e.close()
Expand Down Expand Up @@ -363,7 +380,8 @@ func sendSignal(message *sProto.Message, s signal.Client) error {
}

// SignalOfferAnswer signals either an offer or an answer to remote peer
func SignalOfferAnswer(offerAnswer peer.OfferAnswer, myKey wgtypes.Key, remoteKey wgtypes.Key, s signal.Client, isAnswer bool) error {
func SignalOfferAnswer(offerAnswer peer.OfferAnswer, myKey wgtypes.Key, remoteKey wgtypes.Key, s signal.Client,
isAnswer bool) error {
var t sProto.Body_Type
if isAnswer {
t = sProto.Body_ANSWER
Expand All @@ -374,7 +392,7 @@ func SignalOfferAnswer(offerAnswer peer.OfferAnswer, myKey wgtypes.Key, remoteKe
msg, err := signal.MarshalCredential(myKey, offerAnswer.WgListenPort, remoteKey, &signal.Credential{
UFrag: offerAnswer.IceCredentials.UFrag,
Pwd: offerAnswer.IceCredentials.Pwd,
}, t)
}, t, offerAnswer.RosenpassPubKey, offerAnswer.RosenpassAddr)
if err != nil {
return err
}
Expand Down Expand Up @@ -627,6 +645,7 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
e.acl.ApplyFiltering(networkMap)
}
e.networkSerial = serial

return nil
}

Expand Down Expand Up @@ -796,6 +815,26 @@ func (e *Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, e
PreSharedKey: e.config.PreSharedKey,
}

if e.config.RosenpassEnabled {
lk := []byte(e.config.WgPrivateKey.PublicKey().String())
rk := []byte(wgConfig.RemoteKey)
var keyInput []byte
if string(lk) > string(rk) {
//nolint:gocritic
keyInput = append(lk[:16], rk[:16]...)
} else {
//nolint:gocritic
keyInput = append(rk[:16], lk[:16]...)
}

key, err := wgtypes.NewKey(keyInput)
if err != nil {
return nil, err
}

wgConfig.PreSharedKey = &key
}

// randomize connection timeout
timeout := time.Duration(rand.Intn(PeerConnectionTimeoutMax-PeerConnectionTimeoutMin)+PeerConnectionTimeoutMin) * time.Millisecond
config := peer.ConnConfig{
Expand All @@ -811,6 +850,8 @@ func (e *Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, e
LocalWgPort: e.config.WgPort,
NATExternalIPs: e.parseNATExternalIPMappings(),
UserspaceBind: e.wgInterface.IsUserspaceBind(),
RosenpassPubKey: e.getRosenpassPubKey(),
RosenpassAddr: e.getRosenpassAddr(),
}

peerConn, err := peer.NewConn(config, e.statusRecorder, e.wgProxyFactory, e.mobileDep.TunAdapter, e.mobileDep.IFaceDiscover)
Expand Down Expand Up @@ -842,6 +883,12 @@ func (e *Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, e
return sendSignal(message, e.signal)
})

if e.rpManager != nil {

peerConn.SetOnConnected(e.rpManager.OnConnected)
peerConn.SetOnDisconnected(e.rpManager.OnDisconnected)
}

return peerConn, nil
}

Expand All @@ -867,29 +914,45 @@ func (e *Engine) receiveSignalEvents() {

conn.RegisterProtoSupportMeta(msg.Body.GetFeaturesSupported())

var rosenpassPubKey []byte
rosenpassAddr := ""
if msg.GetBody().GetRosenpassConfig() != nil {
rosenpassPubKey = msg.GetBody().GetRosenpassConfig().GetRosenpassPubKey()
rosenpassAddr = msg.GetBody().GetRosenpassConfig().GetRosenpassServerAddr()
}
conn.OnRemoteOffer(peer.OfferAnswer{
IceCredentials: peer.IceCredentials{
UFrag: remoteCred.UFrag,
Pwd: remoteCred.Pwd,
},
WgListenPort: int(msg.GetBody().GetWgListenPort()),
Version: msg.GetBody().GetNetBirdVersion(),
WgListenPort: int(msg.GetBody().GetWgListenPort()),
Version: msg.GetBody().GetNetBirdVersion(),
RosenpassPubKey: rosenpassPubKey,
RosenpassAddr: rosenpassAddr,
})
case sProto.Body_ANSWER:
remoteCred, err := signal.UnMarshalCredential(msg)
if err != nil {
return err
}

conn.RegisterProtoSupportMeta(msg.Body.GetFeaturesSupported())
conn.RegisterProtoSupportMeta(msg.GetBody().GetFeaturesSupported())

var rosenpassPubKey []byte
rosenpassAddr := ""
if msg.GetBody().GetRosenpassConfig() != nil {
rosenpassPubKey = msg.GetBody().GetRosenpassConfig().GetRosenpassPubKey()
rosenpassAddr = msg.GetBody().GetRosenpassConfig().GetRosenpassServerAddr()
}
conn.OnRemoteAnswer(peer.OfferAnswer{
IceCredentials: peer.IceCredentials{
UFrag: remoteCred.UFrag,
Pwd: remoteCred.Pwd,
},
WgListenPort: int(msg.GetBody().GetWgListenPort()),
Version: msg.GetBody().GetNetBirdVersion(),
WgListenPort: int(msg.GetBody().GetWgListenPort()),
Version: msg.GetBody().GetNetBirdVersion(),
RosenpassPubKey: rosenpassPubKey,
RosenpassAddr: rosenpassAddr,
})
case sProto.Body_CANDIDATE:
candidate, err := ice.UnmarshalCandidate(msg.GetBody().Payload)
Expand Down Expand Up @@ -1000,6 +1063,10 @@ func (e *Engine) close() {
log.Warnf("failed to reset firewall: %s", err)
}
}

if e.rpManager != nil {
_ = e.rpManager.Close()
}
}

func (e *Engine) readInitialSettings() ([]*route.Route, *nbdns.Config, error) {
Expand Down Expand Up @@ -1094,3 +1161,17 @@ func findIPFromInterface(iface *net.Interface) (net.IP, error) {
}
return nil, fmt.Errorf("interface %s don't have an ipv4 address", iface.Name)
}

func (e *Engine) getRosenpassPubKey() []byte {
if e.rpManager != nil {
return e.rpManager.GetPubKey()
}
return nil
}

func (e *Engine) getRosenpassAddr() string {
if e.rpManager != nil {
return e.rpManager.GetAddress().String()
}
return ""
}
Loading

0 comments on commit 5de4acf

Please sign in to comment.