@@ -11,6 +11,7 @@ import (
1111 "fmt"
1212 "log"
1313 "maps"
14+ "sort"
1415 "strings"
1516
1617 greenhousemetav1alpha1 "github.com/cloudoperators/greenhouse/api/meta/v1alpha1"
3031 remoteClusterName string
3132 prefix string
3233 mergeIdenticalUsers bool
34+ authType string
35+ kubeloginPath string
36+ kubeloginExtraArgs []string
3337)
3438
3539func init () {
@@ -41,6 +45,11 @@ func init() {
4145 syncCmd .Flags ().StringVar (& remoteClusterName , "remote-cluster-name" , "" , "name of the remote cluster, if not set all clusters are retrieved" )
4246 syncCmd .Flags ().StringVar (& prefix , "prefix" , "cloudctl" , "prefix for kubeconfig entries. it is used to separate and manage the entries of this tool only" )
4347 syncCmd .Flags ().BoolVar (& mergeIdenticalUsers , "merge-identical-users" , true , "merge identical user information in kubeconfig file so that you only login once for the clusters that share the same auth info" )
48+
49+ // Authentication output style flags
50+ syncCmd .Flags ().StringVar (& authType , "auth-type" , "auth-provider" , "authentication config style to write for users: auth-provider or exec-plugin" )
51+ syncCmd .Flags ().StringVar (& kubeloginPath , "kubelogin-path" , "kubelogin" , "path to kubelogin command when using exec-plugin auth-type" )
52+ syncCmd .Flags ().StringSliceVar (& kubeloginExtraArgs , "kubelogin-extra-args" , nil , "extra arguments to pass to kubelogin exec plugin" )
4453}
4554
4655var syncCmd = & cobra.Command {
@@ -136,7 +145,7 @@ func filterReady(items []v1alpha1.ClusterKubeconfig) []v1alpha1.ClusterKubeconfi
136145 eligible := make ([]v1alpha1.ClusterKubeconfig , 0 , len (items ))
137146 for _ , ckc := range items {
138147 cond := ckc .Status .Conditions .GetConditionByType (greenhousemetav1alpha1 .ReadyCondition )
139- if cond == nil || cond .IsTrue () {
148+ if cond != nil && cond .IsTrue () {
140149 eligible = append (eligible , ckc )
141150 }
142151 }
@@ -160,12 +169,26 @@ func buildIncomingKubeconfig(items []v1alpha1.ClusterKubeconfig) (*clientcmdapi.
160169
161170 // Add all users (auth infos)
162171 for _ , authItem := range ckc .Spec .Kubeconfig .AuthInfo {
163- // Preserve the same data shape; exclude nothing here (merging will handle dedupe)
164- authProvider := authItem .AuthInfo .AuthProvider
165- kubeconfig .AuthInfos [authItem .Name ] = & clientcmdapi.AuthInfo {
166- ClientCertificateData : authItem .AuthInfo .ClientCertificateData ,
167- ClientKeyData : authItem .AuthInfo .ClientKeyData ,
168- AuthProvider : & authProvider ,
172+ // Depending on the selected auth type, keep legacy auth-provider or convert to exec plugin
173+ if strings .EqualFold (authType , "exec-plugin" ) && authItem .AuthInfo .AuthProvider .Name == "oidc" {
174+ execAuth := & clientcmdapi.AuthInfo {
175+ ClientCertificateData : authItem .AuthInfo .ClientCertificateData ,
176+ ClientKeyData : authItem .AuthInfo .ClientKeyData ,
177+ Exec : & clientcmdapi.ExecConfig {
178+ APIVersion : "client.authentication.k8s.io/v1" ,
179+ Command : kubeloginPath ,
180+ Args : buildKubeloginArgs (authItem .AuthInfo .AuthProvider .Config , kubeloginExtraArgs ),
181+ InteractiveMode : clientcmdapi .IfAvailableExecInteractiveMode ,
182+ },
183+ }
184+ kubeconfig .AuthInfos [authItem .Name ] = execAuth
185+ } else {
186+ // Preserve the same data shape; exclude nothing here (merging will handle dedupe)
187+ kubeconfig .AuthInfos [authItem .Name ] = & clientcmdapi.AuthInfo {
188+ ClientCertificateData : authItem .AuthInfo .ClientCertificateData ,
189+ ClientKeyData : authItem .AuthInfo .ClientKeyData ,
190+ AuthProvider : & authItem .AuthInfo .AuthProvider ,
191+ }
169192 }
170193 }
171194
@@ -230,8 +253,27 @@ func authInfoEqual(a, b *clientcmdapi.AuthInfo) bool {
230253 return false
231254 }
232255
256+ // Compare Exec first (new style)
257+ if (a .Exec == nil ) != (b .Exec == nil ) {
258+ return false
259+ }
260+ if a .Exec != nil && b .Exec != nil {
261+ if a .Exec .Command != b .Exec .Command || a .Exec .APIVersion != b .Exec .APIVersion {
262+ return false
263+ }
264+ if len (a .Exec .Args ) != len (b .Exec .Args ) {
265+ return false
266+ }
267+ for i := range a .Exec .Args {
268+ if a .Exec .Args [i ] != b .Exec .Args [i ] {
269+ return false
270+ }
271+ }
272+ return true
273+ }
274+
233275 // Compare AuthProvider, excluding "id-token" and "refresh-token"
234- if a .AuthProvider == nil && b . AuthProvider != nil || a . AuthProvider != nil && b .AuthProvider == nil {
276+ if ( a .AuthProvider == nil ) != ( b .AuthProvider == nil ) {
235277 return false
236278 }
237279 if a .AuthProvider != nil && b .AuthProvider != nil {
@@ -266,6 +308,30 @@ func filterAuthProviderConfig(config map[string]string) map[string]string {
266308// excluding "id-token" and "refresh-token". It uses "client-id", "client-secret",
267309// "auth-request-extra-params", and "extra-scopes" to generate the key.
268310func generateAuthInfoKey (authInfo * clientcmdapi.AuthInfo ) string {
311+ // Exec-based key: derive from stable subset of args to avoid including tokens
312+ if authInfo .Exec != nil {
313+ // Extract known kubelogin flags
314+ var issuer , clientID , clientSecret , extraParams string
315+ var scopes []string
316+ for _ , arg := range authInfo .Exec .Args {
317+ switch {
318+ case strings .HasPrefix (arg , "--oidc-issuer-url=" ):
319+ issuer = strings .TrimPrefix (arg , "--oidc-issuer-url=" )
320+ case strings .HasPrefix (arg , "--oidc-client-id=" ):
321+ clientID = strings .TrimPrefix (arg , "--oidc-client-id=" )
322+ case strings .HasPrefix (arg , "--oidc-client-secret=" ):
323+ clientSecret = strings .TrimPrefix (arg , "--oidc-client-secret=" )
324+ case strings .HasPrefix (arg , "--oidc-extra-scope=" ):
325+ scopes = append (scopes , strings .TrimPrefix (arg , "--oidc-extra-scope=" ))
326+ case strings .HasPrefix (arg , "--oidc-auth-request-extra-params=" ):
327+ extraParams = strings .TrimPrefix (arg , "--oidc-auth-request-extra-params=" )
328+ }
329+ }
330+ sort .Strings (scopes )
331+ data := fmt .Sprintf ("exec:issuer:%s;client-id:%s;client-secret:%s;extra-params:%s;scopes:%s" , issuer , clientID , clientSecret , extraParams , strings .Join (scopes , "," ))
332+ return data
333+ }
334+
269335 if authInfo .AuthProvider == nil {
270336 // For AuthInfos without AuthProvider, use a different unique identifier
271337 // Here, we'll use the hash of ClientCertificateData and ClientKeyData
@@ -288,6 +354,36 @@ func generateAuthInfoKey(authInfo *clientcmdapi.AuthInfo) string {
288354 return data
289355}
290356
357+ // buildKubeloginArgs constructs kubelogin arguments from an oidc auth-provider config and extra args
358+ func buildKubeloginArgs (cfg map [string ]string , extra []string ) []string {
359+ args := []string {"get-token" }
360+ if v := cfg ["idp-issuer-url" ]; v != "" {
361+ args = append (args , "--oidc-issuer-url=" + v )
362+ }
363+ if v := cfg ["client-id" ]; v != "" {
364+ args = append (args , "--oidc-client-id=" + v )
365+ }
366+ if v := cfg ["client-secret" ]; v != "" {
367+ args = append (args , "--oidc-client-secret=" + v )
368+ }
369+ if v := cfg ["extra-scopes" ]; v != "" {
370+ for _ , s := range strings .Split (v , "," ) {
371+ s = strings .TrimSpace (s )
372+ if s != "" {
373+ args = append (args , "--oidc-extra-scope=" + s )
374+ }
375+ }
376+ }
377+ if v := cfg ["auth-request-extra-params" ]; v != "" {
378+ args = append (args , "--oidc-auth-request-extra-params=" + v )
379+ }
380+ // allow caller to inject additional flags
381+ if len (extra ) > 0 {
382+ args = append (args , extra ... )
383+ }
384+ return args
385+ }
386+
291387func mergeKubeconfig (localConfig * clientcmdapi.Config , serverConfig * clientcmdapi.Config ) error {
292388 // Merge Clusters
293389 for serverName , serverCluster := range serverConfig .Clusters {
0 commit comments