From 82d982b0ab69582fd71c58a9b0490e0d7df33e45 Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Fri, 11 Apr 2025 11:34:55 +0200 Subject: [PATCH] [management,client] Add support to configurable prompt login (#3660) --- .../workflows/test-infrastructure-files.yml | 1 + client/internal/auth/pkce_flow.go | 12 +++-- client/internal/auth/pkce_flow_test.go | 49 +++++++++++++++++++ client/internal/pkce_auth.go | 3 ++ infrastructure_files/base.setup.env | 2 + infrastructure_files/management.json.tmpl | 3 +- infrastructure_files/tests/setup.env | 1 + management/proto/management.pb.go | 24 ++++++--- management/proto/management.proto | 2 + management/server/grpcserver.go | 1 + management/server/types/config.go | 2 + 11 files changed, 89 insertions(+), 11 deletions(-) create mode 100644 client/internal/auth/pkce_flow_test.go diff --git a/.github/workflows/test-infrastructure-files.yml b/.github/workflows/test-infrastructure-files.yml index 5a3c6c22e..174b7d205 100644 --- a/.github/workflows/test-infrastructure-files.yml +++ b/.github/workflows/test-infrastructure-files.yml @@ -178,6 +178,7 @@ jobs: grep -A 10 'relay:' docker-compose.yml | egrep 'NB_AUTH_SECRET=.+$' grep -A 7 Relay management.json | grep "rel://$CI_NETBIRD_DOMAIN:33445" grep -A 7 Relay management.json | egrep '"Secret": ".+"' + grep DisablePromptLogin management.json | grep 'true' - name: Install modules run: go mod tidy diff --git a/client/internal/auth/pkce_flow.go b/client/internal/auth/pkce_flow.go index 6c2323412..c5bd84cd5 100644 --- a/client/internal/auth/pkce_flow.go +++ b/client/internal/auth/pkce_flow.go @@ -94,13 +94,17 @@ func (p *PKCEAuthorizationFlow) RequestAuthInfo(ctx context.Context) (AuthFlowIn p.codeVerifier = codeVerifier codeChallenge := createCodeChallenge(codeVerifier) - authURL := p.oAuthConfig.AuthCodeURL( - state, + + params := []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("code_challenge_method", "S256"), oauth2.SetAuthURLParam("code_challenge", codeChallenge), oauth2.SetAuthURLParam("audience", p.providerConfig.Audience), - oauth2.SetAuthURLParam("prompt", "login"), - ) + } + if !p.providerConfig.DisablePromptLogin { + params = append(params, oauth2.SetAuthURLParam("prompt", "login")) + } + + authURL := p.oAuthConfig.AuthCodeURL(state, params...) return AuthFlowInfo{ VerificationURIComplete: authURL, diff --git a/client/internal/auth/pkce_flow_test.go b/client/internal/auth/pkce_flow_test.go new file mode 100644 index 000000000..4510ed338 --- /dev/null +++ b/client/internal/auth/pkce_flow_test.go @@ -0,0 +1,49 @@ +package auth + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/netbirdio/netbird/client/internal" +) + +func TestPromptLogin(t *testing.T) { + tt := []struct { + name string + prompt bool + }{ + {"PromptLogin", true}, + {"NoPromptLogin", false}, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + config := internal.PKCEAuthProviderConfig{ + ClientID: "test-client-id", + Audience: "test-audience", + TokenEndpoint: "https://test-token-endpoint.com/token", + Scope: "openid email profile", + AuthorizationEndpoint: "https://test-auth-endpoint.com/authorize", + RedirectURLs: []string{"http://127.0.0.1:33992/"}, + UseIDToken: true, + DisablePromptLogin: !tc.prompt, + } + pkce, err := NewPKCEAuthorizationFlow(config) + if err != nil { + t.Fatalf("Failed to create PKCEAuthorizationFlow: %v", err) + } + authInfo, err := pkce.RequestAuthInfo(context.Background()) + if err != nil { + t.Fatalf("Failed to request auth info: %v", err) + } + pattern := "prompt=login" + if tc.prompt { + require.Contains(t, authInfo.VerificationURIComplete, pattern) + } else { + require.NotContains(t, authInfo.VerificationURIComplete, pattern) + } + }) + } +} diff --git a/client/internal/pkce_auth.go b/client/internal/pkce_auth.go index ac6734b0c..34eb2df1c 100644 --- a/client/internal/pkce_auth.go +++ b/client/internal/pkce_auth.go @@ -39,6 +39,8 @@ type PKCEAuthProviderConfig struct { UseIDToken bool // ClientCertPair is used for mTLS authentication to the IDP ClientCertPair *tls.Certificate + // DisablePromptLogin makes the PKCE flow to not prompt the user for login + DisablePromptLogin bool } // GetPKCEAuthorizationFlowInfo initialize a PKCEAuthorizationFlow instance and return with it @@ -97,6 +99,7 @@ func GetPKCEAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmURL RedirectURLs: protoPKCEAuthorizationFlow.GetProviderConfig().GetRedirectURLs(), UseIDToken: protoPKCEAuthorizationFlow.GetProviderConfig().GetUseIDToken(), ClientCertPair: clientCert, + DisablePromptLogin: protoPKCEAuthorizationFlow.GetProviderConfig().GetDisablePromptLogin(), }, } diff --git a/infrastructure_files/base.setup.env b/infrastructure_files/base.setup.env index 45dce8d88..4b1376921 100644 --- a/infrastructure_files/base.setup.env +++ b/infrastructure_files/base.setup.env @@ -58,6 +58,7 @@ NETBIRD_TOKEN_SOURCE=${NETBIRD_TOKEN_SOURCE:-accessToken} # PKCE authorization flow NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS=${NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS:-"53000"} NETBIRD_AUTH_PKCE_USE_ID_TOKEN=${NETBIRD_AUTH_PKCE_USE_ID_TOKEN:-false} +NETBIRD_AUTH_PKCE_DISABLE_PROMPT_LOGIN=${NETBIRD_AUTH_PKCE_DISABLE_PROMPT_LOGIN:-false} NETBIRD_AUTH_PKCE_AUDIENCE=$NETBIRD_AUTH_AUDIENCE # Dashboard @@ -120,6 +121,7 @@ export NETBIRD_AUTH_DEVICE_AUTH_SCOPE export NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN export NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT export NETBIRD_AUTH_PKCE_USE_ID_TOKEN +export NETBIRD_AUTH_PKCE_DISABLE_PROMPT_LOGIN export NETBIRD_AUTH_PKCE_AUDIENCE export NETBIRD_DASH_AUTH_USE_AUDIENCE export NETBIRD_DASH_AUTH_AUDIENCE diff --git a/infrastructure_files/management.json.tmpl b/infrastructure_files/management.json.tmpl index 5cbf2b4d3..aa1739c61 100644 --- a/infrastructure_files/management.json.tmpl +++ b/infrastructure_files/management.json.tmpl @@ -94,7 +94,8 @@ "TokenEndpoint": "$NETBIRD_AUTH_TOKEN_ENDPOINT", "Scope": "$NETBIRD_AUTH_SUPPORTED_SCOPES", "RedirectURLs": [$NETBIRD_AUTH_PKCE_REDIRECT_URLS], - "UseIDToken": $NETBIRD_AUTH_PKCE_USE_ID_TOKEN + "UseIDToken": $NETBIRD_AUTH_PKCE_USE_ID_TOKEN, + "DisablePromptLogin": $NETBIRD_AUTH_PKCE_DISABLE_PROMPT_LOGIN } } } diff --git a/infrastructure_files/tests/setup.env b/infrastructure_files/tests/setup.env index 5d774fbd1..2945e1c43 100644 --- a/infrastructure_files/tests/setup.env +++ b/infrastructure_files/tests/setup.env @@ -27,3 +27,4 @@ NETBIRD_STORE_CONFIG_ENGINE=$CI_NETBIRD_STORE_CONFIG_ENGINE NETBIRD_MGMT_IDP_SIGNKEY_REFRESH=$CI_NETBIRD_MGMT_IDP_SIGNKEY_REFRESH NETBIRD_TURN_EXTERNAL_IP=1.2.3.4 NETBIRD_RELAY_PORT=33445 +NETBIRD_AUTH_PKCE_DISABLE_PROMPT_LOGIN=true diff --git a/management/proto/management.pb.go b/management/proto/management.pb.go index 83780762b..f3f53bfd4 100644 --- a/management/proto/management.pb.go +++ b/management/proto/management.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v4.24.3 +// protoc v3.21.9 // source: management.proto package proto @@ -1447,11 +1447,11 @@ type FlowConfig struct { TokenSignature string `protobuf:"bytes,3,opt,name=tokenSignature,proto3" json:"tokenSignature,omitempty"` Interval *durationpb.Duration `protobuf:"bytes,4,opt,name=interval,proto3" json:"interval,omitempty"` Enabled bool `protobuf:"varint,5,opt,name=enabled,proto3" json:"enabled,omitempty"` - // Counters determines if flow packets and bytes counters should be sent + // counters determines if flow packets and bytes counters should be sent Counters bool `protobuf:"varint,6,opt,name=counters,proto3" json:"counters,omitempty"` - // ExitNodeCollection determines if event collection on exit nodes should be enabled + // exitNodeCollection determines if event collection on exit nodes should be enabled ExitNodeCollection bool `protobuf:"varint,7,opt,name=exitNodeCollection,proto3" json:"exitNodeCollection,omitempty"` - // DnsCollection determines if DNS event collection should be enabled + // dnsCollection determines if DNS event collection should be enabled DnsCollection bool `protobuf:"varint,8,opt,name=dnsCollection,proto3" json:"dnsCollection,omitempty"` } @@ -2192,6 +2192,8 @@ type ProviderConfig struct { AuthorizationEndpoint string `protobuf:"bytes,9,opt,name=AuthorizationEndpoint,proto3" json:"AuthorizationEndpoint,omitempty"` // RedirectURLs handles authorization code from IDP manager RedirectURLs []string `protobuf:"bytes,10,rep,name=RedirectURLs,proto3" json:"RedirectURLs,omitempty"` + // DisablePromptLogin makes the PKCE flow to not prompt the user for login + DisablePromptLogin bool `protobuf:"varint,11,opt,name=DisablePromptLogin,proto3" json:"DisablePromptLogin,omitempty"` } func (x *ProviderConfig) Reset() { @@ -2296,6 +2298,13 @@ func (x *ProviderConfig) GetRedirectURLs() []string { return nil } +func (x *ProviderConfig) GetDisablePromptLogin() bool { + if x != nil { + return x.DisablePromptLogin + } + return false +} + // Route represents a route.Route object type Route struct { state protoimpl.MessageState @@ -3578,7 +3587,7 @@ var file_management_proto_rawDesc = []byte{ 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xea, 0x02, 0x0a, 0x0e, 0x50, 0x72, 0x6f, + 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x9a, 0x03, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, @@ -3601,7 +3610,10 @@ var file_management_proto_rawDesc = []byte{ 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, - 0x74, 0x55, 0x52, 0x4c, 0x73, 0x22, 0xed, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, + 0x74, 0x55, 0x52, 0x4c, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, + 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x12, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x22, 0xed, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x65, 0x74, diff --git a/management/proto/management.proto b/management/proto/management.proto index f7d11fdac..0f1cdb97a 100644 --- a/management/proto/management.proto +++ b/management/proto/management.proto @@ -372,6 +372,8 @@ message ProviderConfig { string AuthorizationEndpoint = 9; // RedirectURLs handles authorization code from IDP manager repeated string RedirectURLs = 10; + // DisablePromptLogin makes the PKCE flow to not prompt the user for login + bool DisablePromptLogin = 11; } // Route represents a route.Route object diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go index dba5ab13b..a7ed639c3 100644 --- a/management/server/grpcserver.go +++ b/management/server/grpcserver.go @@ -828,6 +828,7 @@ func (s *GRPCServer) GetPKCEAuthorizationFlow(ctx context.Context, req *proto.En Scope: s.config.PKCEAuthorizationFlow.ProviderConfig.Scope, RedirectURLs: s.config.PKCEAuthorizationFlow.ProviderConfig.RedirectURLs, UseIDToken: s.config.PKCEAuthorizationFlow.ProviderConfig.UseIDToken, + DisablePromptLogin: s.config.PKCEAuthorizationFlow.ProviderConfig.DisablePromptLogin, }, } diff --git a/management/server/types/config.go b/management/server/types/config.go index d2e418264..7a16b20a1 100644 --- a/management/server/types/config.go +++ b/management/server/types/config.go @@ -154,6 +154,8 @@ type ProviderConfig struct { UseIDToken bool // RedirectURL handles authorization code from IDP manager RedirectURLs []string + // DisablePromptLogin makes the PKCE flow to not prompt the user for login + DisablePromptLogin bool } // StoreConfig contains Store configuration