mirror of
https://github.com/netbirdio/netbird.git
synced 2024-11-26 18:13:40 +01:00
7794b744f8
Enhance the user experience by enabling authentication to Netbird using Single Sign-On (SSO) with any Identity Provider (IDP) provider. Current client offers this capability through the Device Authorization Flow, however, is not widely supported by many IDPs, and even some that do support it do not provide a complete verification URL. To address these challenges, this pull request enable Authorization Code Flow with Proof Key for Code Exchange (PKCE) for client logins, which is a more widely adopted and secure approach to facilitate SSO with various IDP providers.
129 lines
4.5 KiB
Go
129 lines
4.5 KiB
Go
package internal
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
|
|
mgm "github.com/netbirdio/netbird/management/client"
|
|
)
|
|
|
|
// PKCEAuthorizationFlow represents PKCE Authorization Flow information
|
|
type PKCEAuthorizationFlow struct {
|
|
ProviderConfig PKCEAuthProviderConfig
|
|
}
|
|
|
|
// PKCEAuthProviderConfig has all attributes needed to initiate pkce authorization flow
|
|
type PKCEAuthProviderConfig struct {
|
|
// ClientID An IDP application client id
|
|
ClientID string
|
|
// ClientSecret An IDP application client secret
|
|
ClientSecret string
|
|
// Audience An Audience for to authorization validation
|
|
Audience string
|
|
// TokenEndpoint is the endpoint of an IDP manager where clients can obtain access token
|
|
TokenEndpoint string
|
|
// AuthorizationEndpoint is the endpoint of an IDP manager where clients can obtain authorization code
|
|
AuthorizationEndpoint string
|
|
// Scopes provides the scopes to be included in the token request
|
|
Scope string
|
|
// RedirectURL handles authorization code from IDP manager
|
|
RedirectURLs []string
|
|
// UseIDToken indicates if the id token should be used for authentication
|
|
UseIDToken bool
|
|
}
|
|
|
|
// GetPKCEAuthorizationFlowInfo initialize a PKCEAuthorizationFlow instance and return with it
|
|
func GetPKCEAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmURL *url.URL) (PKCEAuthorizationFlow, error) {
|
|
// validate our peer's Wireguard PRIVATE key
|
|
myPrivateKey, err := wgtypes.ParseKey(privateKey)
|
|
if err != nil {
|
|
log.Errorf("failed parsing Wireguard key %s: [%s]", privateKey, err.Error())
|
|
return PKCEAuthorizationFlow{}, err
|
|
}
|
|
|
|
var mgmTLSEnabled bool
|
|
if mgmURL.Scheme == "https" {
|
|
mgmTLSEnabled = true
|
|
}
|
|
|
|
log.Debugf("connecting to Management Service %s", mgmURL.String())
|
|
mgmClient, err := mgm.NewClient(ctx, mgmURL.Host, myPrivateKey, mgmTLSEnabled)
|
|
if err != nil {
|
|
log.Errorf("failed connecting to Management Service %s %v", mgmURL.String(), err)
|
|
return PKCEAuthorizationFlow{}, err
|
|
}
|
|
log.Debugf("connected to the Management service %s", mgmURL.String())
|
|
|
|
defer func() {
|
|
err = mgmClient.Close()
|
|
if err != nil {
|
|
log.Warnf("failed to close the Management service client %v", err)
|
|
}
|
|
}()
|
|
|
|
serverKey, err := mgmClient.GetServerPublicKey()
|
|
if err != nil {
|
|
log.Errorf("failed while getting Management Service public key: %v", err)
|
|
return PKCEAuthorizationFlow{}, err
|
|
}
|
|
|
|
protoPKCEAuthorizationFlow, err := mgmClient.GetPKCEAuthorizationFlow(*serverKey)
|
|
if err != nil {
|
|
if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound {
|
|
log.Warnf("server couldn't find pkce flow, contact admin: %v", err)
|
|
return PKCEAuthorizationFlow{}, err
|
|
}
|
|
log.Errorf("failed to retrieve pkce flow: %v", err)
|
|
return PKCEAuthorizationFlow{}, err
|
|
}
|
|
|
|
authFlow := PKCEAuthorizationFlow{
|
|
ProviderConfig: PKCEAuthProviderConfig{
|
|
Audience: protoPKCEAuthorizationFlow.GetProviderConfig().GetAudience(),
|
|
ClientID: protoPKCEAuthorizationFlow.GetProviderConfig().GetClientID(),
|
|
ClientSecret: protoPKCEAuthorizationFlow.GetProviderConfig().GetClientSecret(),
|
|
TokenEndpoint: protoPKCEAuthorizationFlow.GetProviderConfig().GetTokenEndpoint(),
|
|
AuthorizationEndpoint: protoPKCEAuthorizationFlow.GetProviderConfig().GetAuthorizationEndpoint(),
|
|
Scope: protoPKCEAuthorizationFlow.GetProviderConfig().GetScope(),
|
|
RedirectURLs: protoPKCEAuthorizationFlow.GetProviderConfig().GetRedirectURLs(),
|
|
UseIDToken: protoPKCEAuthorizationFlow.GetProviderConfig().GetUseIDToken(),
|
|
},
|
|
}
|
|
|
|
err = isPKCEProviderConfigValid(authFlow.ProviderConfig)
|
|
if err != nil {
|
|
return PKCEAuthorizationFlow{}, err
|
|
}
|
|
|
|
return authFlow, nil
|
|
}
|
|
|
|
func isPKCEProviderConfigValid(config PKCEAuthProviderConfig) error {
|
|
errorMSGFormat := "invalid provider configuration received from management: %s value is empty. Contact your NetBird administrator"
|
|
if config.Audience == "" {
|
|
return fmt.Errorf(errorMSGFormat, "Audience")
|
|
}
|
|
if config.ClientID == "" {
|
|
return fmt.Errorf(errorMSGFormat, "Client ID")
|
|
}
|
|
if config.TokenEndpoint == "" {
|
|
return fmt.Errorf(errorMSGFormat, "Token Endpoint")
|
|
}
|
|
if config.AuthorizationEndpoint == "" {
|
|
return fmt.Errorf(errorMSGFormat, "Authorization Auth Endpoint")
|
|
}
|
|
if config.Scope == "" {
|
|
return fmt.Errorf(errorMSGFormat, "PKCE Auth Scopes")
|
|
}
|
|
if config.RedirectURLs == nil {
|
|
return fmt.Errorf(errorMSGFormat, "PKCE Redirect URLs")
|
|
}
|
|
return nil
|
|
}
|