From 2f6fe2d7e15b58f54b6097ddbfcc1cfc69b2d8ad Mon Sep 17 00:00:00 2001 From: foosec Date: Mon, 27 May 2024 14:01:21 +0200 Subject: [PATCH 1/8] Add mTLS support for SSO --- client/android/login.go | 2 +- client/internal/auth/oauth.go | 2 +- client/internal/auth/pkce_flow.go | 13 +++++++++++++ client/internal/config.go | 30 ++++++++++++++++++++++++++++++ client/internal/pkce_auth.go | 6 +++++- 5 files changed, 50 insertions(+), 3 deletions(-) diff --git a/client/android/login.go b/client/android/login.go index 3840c75c112..f5ee4aa6728 100644 --- a/client/android/login.go +++ b/client/android/login.go @@ -86,7 +86,7 @@ func (a *Auth) saveConfigIfSSOSupported() (bool, error) { err := a.withBackOff(a.ctx, func() (err error) { _, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL) if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.NotFound || s.Code() == codes.Unimplemented) { - _, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL) + _, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL, nil) s, ok := gstatus.FromError(err) if !ok { return err diff --git a/client/internal/auth/oauth.go b/client/internal/auth/oauth.go index 7467584a34b..d0f23644063 100644 --- a/client/internal/auth/oauth.go +++ b/client/internal/auth/oauth.go @@ -81,7 +81,7 @@ func NewOAuthFlow(ctx context.Context, config *internal.Config, isLinuxDesktopCl // authenticateWithPKCEFlow initializes the Proof Key for Code Exchange flow auth flow func authenticateWithPKCEFlow(ctx context.Context, config *internal.Config) (OAuthFlow, error) { - pkceFlowInfo, err := internal.GetPKCEAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL) + pkceFlowInfo, err := internal.GetPKCEAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL, config.ClientCertKeyPair) if err != nil { return nil, fmt.Errorf("getting pkce authorization flow info failed with error: %v", err) } diff --git a/client/internal/auth/pkce_flow.go b/client/internal/auth/pkce_flow.go index 32f5383d36e..ff2654588cb 100644 --- a/client/internal/auth/pkce_flow.go +++ b/client/internal/auth/pkce_flow.go @@ -4,6 +4,7 @@ import ( "context" "crypto/sha256" "crypto/subtle" + "crypto/tls" "encoding/base64" "errors" "fmt" @@ -143,6 +144,18 @@ func (p *PKCEAuthorizationFlow) WaitToken(ctx context.Context, _ AuthFlowInfo) ( func (p *PKCEAuthorizationFlow) startServer(server *http.Server, tokenChan chan<- *oauth2.Token, errChan chan<- error) { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + cert := p.providerConfig.ClientCertPair + if cert != nil { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + Certificates: []tls.Certificate{*cert}, + }, + } + ssl_client := &http.Client{Transport: tr} + ctx := context.WithValue(req.Context(), oauth2.HTTPClient, ssl_client) + req = req.WithContext(ctx) + } + token, err := p.handleRequest(req) if err != nil { renderPKCEFlowTmpl(w, err) diff --git a/client/internal/config.go b/client/internal/config.go index 66721cd21f8..a0fbbbbe257 100644 --- a/client/internal/config.go +++ b/client/internal/config.go @@ -2,6 +2,7 @@ package internal import ( "context" + "crypto/tls" "fmt" "net/url" "os" @@ -53,6 +54,8 @@ type ConfigInput struct { NetworkMonitor *bool DisableAutoConnect *bool ExtraIFaceBlackList []string + ClientCertPath string + ClientCertKeyPath string } // Config Configuration type @@ -95,6 +98,14 @@ type Config struct { // DisableAutoConnect determines whether the client should not start with the service // it's set to false by default due to backwards compatibility DisableAutoConnect bool + + //Path to a certificate used for mTLS authentication + ClientCertPath string + + //Path to corresponding private key of ClientCertPath + ClientCertKeyPath string + + ClientCertKeyPair *tls.Certificate `json:"-"` } // ReadConfig read config file and return with Config. If it is not exists create a new with default values @@ -357,6 +368,25 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) { updated = true } + if input.ClientCertKeyPath != "" { + config.ClientCertKeyPath = input.ClientCertKeyPath + updated = true + } + + if input.ClientCertPath != "" { + config.ClientCertPath = input.ClientCertPath + updated = true + } + + if config.ClientCertPath != "" && config.ClientCertKeyPath != "" { + cert, err := tls.LoadX509KeyPair(config.ClientCertPath, config.ClientCertKeyPath) + if err != nil { + log.Error("Failed to load mTLS cert/key pair: ", err) + } else { + config.ClientCertKeyPair = &cert + log.Info("Loaded client mTLS cert/key pair") + } + } return updated, nil } diff --git a/client/internal/pkce_auth.go b/client/internal/pkce_auth.go index a35dacc77be..6f714889fea 100644 --- a/client/internal/pkce_auth.go +++ b/client/internal/pkce_auth.go @@ -2,6 +2,7 @@ package internal import ( "context" + "crypto/tls" "fmt" "net/url" @@ -36,10 +37,12 @@ type PKCEAuthProviderConfig struct { RedirectURLs []string // UseIDToken indicates if the id token should be used for authentication UseIDToken bool + //ClientCertPair is used for mTLS authentication to the IDP + ClientCertPair *tls.Certificate } // GetPKCEAuthorizationFlowInfo initialize a PKCEAuthorizationFlow instance and return with it -func GetPKCEAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmURL *url.URL) (PKCEAuthorizationFlow, error) { +func GetPKCEAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmURL *url.URL, clientCert *tls.Certificate) (PKCEAuthorizationFlow, error) { // validate our peer's Wireguard PRIVATE key myPrivateKey, err := wgtypes.ParseKey(privateKey) if err != nil { @@ -93,6 +96,7 @@ func GetPKCEAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmURL Scope: protoPKCEAuthorizationFlow.GetProviderConfig().GetScope(), RedirectURLs: protoPKCEAuthorizationFlow.GetProviderConfig().GetRedirectURLs(), UseIDToken: protoPKCEAuthorizationFlow.GetProviderConfig().GetUseIDToken(), + ClientCertPair: clientCert, }, } From 5e27316dea878142a8fc357a1a5bf3e6d95a8e21 Mon Sep 17 00:00:00 2001 From: foosec Date: Mon, 27 May 2024 14:13:02 +0200 Subject: [PATCH 2/8] Fix function call for changed signature due to mTLS addition. Could in future also add mTLs configuration support for android and iOS clients --- client/android/login.go | 4 ++-- client/ios/NetBirdSDK/login.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/android/login.go b/client/android/login.go index f5ee4aa6728..bb61edfa8e2 100644 --- a/client/android/login.go +++ b/client/android/login.go @@ -84,9 +84,9 @@ func (a *Auth) SaveConfigIfSSOSupported(listener SSOListener) { func (a *Auth) saveConfigIfSSOSupported() (bool, error) { supportsSSO := true err := a.withBackOff(a.ctx, func() (err error) { - _, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL) + _, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL, nil) if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.NotFound || s.Code() == codes.Unimplemented) { - _, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL, nil) + _, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL) s, ok := gstatus.FromError(err) if !ok { return err diff --git a/client/ios/NetBirdSDK/login.go b/client/ios/NetBirdSDK/login.go index 3572aa310c2..ff637edd48a 100644 --- a/client/ios/NetBirdSDK/login.go +++ b/client/ios/NetBirdSDK/login.go @@ -74,7 +74,7 @@ func (a *Auth) SaveConfigIfSSOSupported() (bool, error) { err := a.withBackOff(a.ctx, func() (err error) { _, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL) if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.NotFound || s.Code() == codes.Unimplemented) { - _, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL) + _, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL, nil) if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.NotFound || s.Code() == codes.Unimplemented) { supportsSSO = false err = nil From 4b36d14faee8e705146b6df391fe22a160c2f460 Mon Sep 17 00:00:00 2001 From: foosec Date: Mon, 27 May 2024 14:01:21 +0200 Subject: [PATCH 3/8] Add mTLS support for SSO --- client/android/login.go | 2 +- client/internal/auth/oauth.go | 2 +- client/internal/auth/pkce_flow.go | 13 +++++++++++ client/internal/config.go | 38 +++++++++++++++++++++++++++++++ client/internal/pkce_auth.go | 6 ++++- 5 files changed, 58 insertions(+), 3 deletions(-) diff --git a/client/android/login.go b/client/android/login.go index 3840c75c112..f5ee4aa6728 100644 --- a/client/android/login.go +++ b/client/android/login.go @@ -86,7 +86,7 @@ func (a *Auth) saveConfigIfSSOSupported() (bool, error) { err := a.withBackOff(a.ctx, func() (err error) { _, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL) if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.NotFound || s.Code() == codes.Unimplemented) { - _, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL) + _, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL, nil) s, ok := gstatus.FromError(err) if !ok { return err diff --git a/client/internal/auth/oauth.go b/client/internal/auth/oauth.go index 7467584a34b..d0f23644063 100644 --- a/client/internal/auth/oauth.go +++ b/client/internal/auth/oauth.go @@ -81,7 +81,7 @@ func NewOAuthFlow(ctx context.Context, config *internal.Config, isLinuxDesktopCl // authenticateWithPKCEFlow initializes the Proof Key for Code Exchange flow auth flow func authenticateWithPKCEFlow(ctx context.Context, config *internal.Config) (OAuthFlow, error) { - pkceFlowInfo, err := internal.GetPKCEAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL) + pkceFlowInfo, err := internal.GetPKCEAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL, config.ClientCertKeyPair) if err != nil { return nil, fmt.Errorf("getting pkce authorization flow info failed with error: %v", err) } diff --git a/client/internal/auth/pkce_flow.go b/client/internal/auth/pkce_flow.go index 32f5383d36e..ff2654588cb 100644 --- a/client/internal/auth/pkce_flow.go +++ b/client/internal/auth/pkce_flow.go @@ -4,6 +4,7 @@ import ( "context" "crypto/sha256" "crypto/subtle" + "crypto/tls" "encoding/base64" "errors" "fmt" @@ -143,6 +144,18 @@ func (p *PKCEAuthorizationFlow) WaitToken(ctx context.Context, _ AuthFlowInfo) ( func (p *PKCEAuthorizationFlow) startServer(server *http.Server, tokenChan chan<- *oauth2.Token, errChan chan<- error) { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + cert := p.providerConfig.ClientCertPair + if cert != nil { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + Certificates: []tls.Certificate{*cert}, + }, + } + ssl_client := &http.Client{Transport: tr} + ctx := context.WithValue(req.Context(), oauth2.HTTPClient, ssl_client) + req = req.WithContext(ctx) + } + token, err := p.handleRequest(req) if err != nil { renderPKCEFlowTmpl(w, err) diff --git a/client/internal/config.go b/client/internal/config.go index 461dcdd9650..462ef710910 100644 --- a/client/internal/config.go +++ b/client/internal/config.go @@ -2,6 +2,7 @@ package internal import ( "context" + "crypto/tls" "fmt" "net/url" "os" @@ -56,7 +57,12 @@ type ConfigInput struct { NetworkMonitor *bool DisableAutoConnect *bool ExtraIFaceBlackList []string +<<<<<<< HEAD DNSRouteInterval *time.Duration +======= + ClientCertPath string + ClientCertKeyPath string +>>>>>>> 2f6fe2d7 (Add mTLS support for SSO) } // Config Configuration type @@ -100,8 +106,18 @@ type Config struct { // it's set to false by default due to backwards compatibility DisableAutoConnect bool +<<<<<<< HEAD // DNSRouteInterval is the interval in which the DNS routes are updated DNSRouteInterval time.Duration +======= + //Path to a certificate used for mTLS authentication + ClientCertPath string + + //Path to corresponding private key of ClientCertPath + ClientCertKeyPath string + + ClientCertKeyPair *tls.Certificate `json:"-"` +>>>>>>> 2f6fe2d7 (Add mTLS support for SSO) } // ReadConfig read config file and return with Config. If it is not exists create a new with default values @@ -373,6 +389,7 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) { updated = true } +<<<<<<< HEAD if input.DNSRouteInterval != nil && *input.DNSRouteInterval != config.DNSRouteInterval { log.Infof("updating DNS route interval to %s (old value %s)", input.DNSRouteInterval.String(), config.DNSRouteInterval.String()) @@ -385,6 +402,27 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) { } +======= + if input.ClientCertKeyPath != "" { + config.ClientCertKeyPath = input.ClientCertKeyPath + updated = true + } + + if input.ClientCertPath != "" { + config.ClientCertPath = input.ClientCertPath + updated = true + } + + if config.ClientCertPath != "" && config.ClientCertKeyPath != "" { + cert, err := tls.LoadX509KeyPair(config.ClientCertPath, config.ClientCertKeyPath) + if err != nil { + log.Error("Failed to load mTLS cert/key pair: ", err) + } else { + config.ClientCertKeyPair = &cert + log.Info("Loaded client mTLS cert/key pair") + } + } +>>>>>>> 2f6fe2d7 (Add mTLS support for SSO) return updated, nil } diff --git a/client/internal/pkce_auth.go b/client/internal/pkce_auth.go index a35dacc77be..6f714889fea 100644 --- a/client/internal/pkce_auth.go +++ b/client/internal/pkce_auth.go @@ -2,6 +2,7 @@ package internal import ( "context" + "crypto/tls" "fmt" "net/url" @@ -36,10 +37,12 @@ type PKCEAuthProviderConfig struct { RedirectURLs []string // UseIDToken indicates if the id token should be used for authentication UseIDToken bool + //ClientCertPair is used for mTLS authentication to the IDP + ClientCertPair *tls.Certificate } // GetPKCEAuthorizationFlowInfo initialize a PKCEAuthorizationFlow instance and return with it -func GetPKCEAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmURL *url.URL) (PKCEAuthorizationFlow, error) { +func GetPKCEAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmURL *url.URL, clientCert *tls.Certificate) (PKCEAuthorizationFlow, error) { // validate our peer's Wireguard PRIVATE key myPrivateKey, err := wgtypes.ParseKey(privateKey) if err != nil { @@ -93,6 +96,7 @@ func GetPKCEAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmURL Scope: protoPKCEAuthorizationFlow.GetProviderConfig().GetScope(), RedirectURLs: protoPKCEAuthorizationFlow.GetProviderConfig().GetRedirectURLs(), UseIDToken: protoPKCEAuthorizationFlow.GetProviderConfig().GetUseIDToken(), + ClientCertPair: clientCert, }, } From 9d6dde1ee07a2a8cc4c978799782bda8ee5712d1 Mon Sep 17 00:00:00 2001 From: foosec Date: Mon, 24 Jun 2024 16:35:38 +0200 Subject: [PATCH 4/8] Merge upstream changes --- client/internal/config.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/internal/config.go b/client/internal/config.go index 462ef710910..735b2f2add9 100644 --- a/client/internal/config.go +++ b/client/internal/config.go @@ -106,10 +106,9 @@ type Config struct { // it's set to false by default due to backwards compatibility DisableAutoConnect bool -<<<<<<< HEAD // DNSRouteInterval is the interval in which the DNS routes are updated DNSRouteInterval time.Duration -======= + //Path to a certificate used for mTLS authentication ClientCertPath string @@ -117,7 +116,6 @@ type Config struct { ClientCertKeyPath string ClientCertKeyPair *tls.Certificate `json:"-"` ->>>>>>> 2f6fe2d7 (Add mTLS support for SSO) } // ReadConfig read config file and return with Config. If it is not exists create a new with default values From 0e84bd43cdbe684e1e1f711b82ab2cdcde94a094 Mon Sep 17 00:00:00 2001 From: foosec Date: Mon, 24 Jun 2024 16:45:57 +0200 Subject: [PATCH 5/8] Fix merge conflict --- client/internal/config.go | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/client/internal/config.go b/client/internal/config.go index c3482c932b4..74326e823cf 100644 --- a/client/internal/config.go +++ b/client/internal/config.go @@ -57,17 +57,11 @@ type ConfigInput struct { NetworkMonitor *bool DisableAutoConnect *bool ExtraIFaceBlackList []string -<<<<<<< HEAD ClientCertPath string ClientCertKeyPath string -======= -<<<<<<< HEAD DNSRouteInterval *time.Duration -======= ClientCertPath string ClientCertKeyPath string ->>>>>>> 2f6fe2d7 (Add mTLS support for SSO) ->>>>>>> main } // Config Configuration type @@ -391,9 +385,6 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) { updated = true } -<<<<<<< HEAD -======= -<<<<<<< HEAD if input.DNSRouteInterval != nil && *input.DNSRouteInterval != config.DNSRouteInterval { log.Infof("updating DNS route interval to %s (old value %s)", input.DNSRouteInterval.String(), config.DNSRouteInterval.String()) @@ -406,8 +397,6 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) { } -======= ->>>>>>> main if input.ClientCertKeyPath != "" { config.ClientCertKeyPath = input.ClientCertKeyPath updated = true @@ -427,10 +416,7 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) { log.Info("Loaded client mTLS cert/key pair") } } -<<<<<<< HEAD -======= ->>>>>>> 2f6fe2d7 (Add mTLS support for SSO) ->>>>>>> main + return updated, nil } From 35f206f7098373931ea4bc5c0a96c146bc7879ff Mon Sep 17 00:00:00 2001 From: foosec Date: Mon, 24 Jun 2024 16:47:34 +0200 Subject: [PATCH 6/8] Fix duplicate field after merge --- client/internal/config.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/internal/config.go b/client/internal/config.go index 74326e823cf..725703c437f 100644 --- a/client/internal/config.go +++ b/client/internal/config.go @@ -57,8 +57,6 @@ type ConfigInput struct { NetworkMonitor *bool DisableAutoConnect *bool ExtraIFaceBlackList []string - ClientCertPath string - ClientCertKeyPath string DNSRouteInterval *time.Duration ClientCertPath string ClientCertKeyPath string From 067dbc93a05b6cd4cce6fe25345da90ac80b927b Mon Sep 17 00:00:00 2001 From: foosec Date: Tue, 25 Jun 2024 18:59:47 +0200 Subject: [PATCH 7/8] Fix wrong function call signature in GetDeviceAuthorizationFlowInfo --- client/android/login.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/android/login.go b/client/android/login.go index 4a38b671372..bb61edfa8e2 100644 --- a/client/android/login.go +++ b/client/android/login.go @@ -86,7 +86,7 @@ func (a *Auth) saveConfigIfSSOSupported() (bool, error) { err := a.withBackOff(a.ctx, func() (err error) { _, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL, nil) if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.NotFound || s.Code() == codes.Unimplemented) { - _, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL, nil) + _, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL) s, ok := gstatus.FromError(err) if !ok { return err From 396c49916d9563634ed71597037c6dcc586b6156 Mon Sep 17 00:00:00 2001 From: bcmmbaga Date: Tue, 13 Aug 2024 17:38:36 +0300 Subject: [PATCH 8/8] Refactor variable to follow Go naming conventions --- client/internal/auth/pkce_flow.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/internal/auth/pkce_flow.go b/client/internal/auth/pkce_flow.go index ff2654588cb..71ff6de4134 100644 --- a/client/internal/auth/pkce_flow.go +++ b/client/internal/auth/pkce_flow.go @@ -151,8 +151,8 @@ func (p *PKCEAuthorizationFlow) startServer(server *http.Server, tokenChan chan< Certificates: []tls.Certificate{*cert}, }, } - ssl_client := &http.Client{Transport: tr} - ctx := context.WithValue(req.Context(), oauth2.HTTPClient, ssl_client) + sslClient := &http.Client{Transport: tr} + ctx := context.WithValue(req.Context(), oauth2.HTTPClient, sslClient) req = req.WithContext(ctx) }