@@ -32,10 +32,15 @@ import (
3232 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3333 clusterv1alpha1 "open-cluster-management.io/api/cluster/v1alpha1"
3434
35+ "encoding/json"
3536 "k8s.io/apimachinery/pkg/labels"
37+ "k8s.io/apimachinery/pkg/runtime"
38+ "k8s.io/apimachinery/pkg/runtime/schema"
39+ "k8s.io/apimachinery/pkg/runtime/serializer"
3640 "k8s.io/apimachinery/pkg/types"
3741 "k8s.io/apimachinery/pkg/util/wait"
3842 "k8s.io/client-go/kubernetes"
43+ "k8s.io/client-go/rest"
3944 psapi "k8s.io/pod-security-admission/api"
4045 "sigs.k8s.io/controller-runtime/pkg/client"
4146 dynclient "sigs.k8s.io/controller-runtime/pkg/client"
@@ -3179,3 +3184,204 @@ func (f *Framework) AssertNodeNameIsInTargetAndFactIdentifierInCM(nodes []core.N
31793184 }
31803185 return nil
31813186}
3187+ // OAuthClientConfig holds the token settings for an OAuth client so they can be restored later.
3188+ // Pointer fields are nil when the client did not have that field set; restore will remove the field instead of setting 0.
3189+ type OAuthClientConfig struct {
3190+ Name string
3191+ AccessTokenMaxAgeSeconds * int64
3192+ AccessTokenInactivityTimeoutSeconds * int64
3193+ }
3194+
3195+ // GetOAuthClientConfigs lists all OAuth clients and returns their current token config (no patching).
3196+ func (f * Framework ) GetOAuthClientConfigs () ([]OAuthClientConfig , error ) {
3197+ return getOAuthClientConfigs (f )
3198+ }
3199+
3200+ func getOAuthClientConfigs (f * Framework ) ([]OAuthClientConfig , error ) {
3201+ oauthConfig := * f .KubeConfig
3202+ oauthConfig .GroupVersion = & schema.GroupVersion {Group : "oauth.openshift.io" , Version : "v1" }
3203+ oauthConfig .APIPath = "/apis"
3204+ oauthConfig .NegotiatedSerializer = serializer .NewCodecFactory (runtime .NewScheme ())
3205+ restClient , err := rest .RESTClientFor (& oauthConfig )
3206+ if err != nil {
3207+ return nil , fmt .Errorf ("failed to create REST client for oauth: %w" , err )
3208+ }
3209+
3210+ oauthClientListBytes , err := restClient .Get ().
3211+ Resource ("oauthclients" ).
3212+ Do (context .TODO ()).
3213+ Raw ()
3214+ if err != nil {
3215+ return nil , fmt .Errorf ("failed to list oauthclients: %w" , err )
3216+ }
3217+
3218+ var oauthClientList map [string ]interface {}
3219+ if err := json .Unmarshal (oauthClientListBytes , & oauthClientList ); err != nil {
3220+ return nil , fmt .Errorf ("failed to parse oauthclient list response: %w" , err )
3221+ }
3222+
3223+ items , ok := oauthClientList ["items" ].([]interface {})
3224+ if ! ok {
3225+ return nil , fmt .Errorf ("failed to extract items from oauthclient list" )
3226+ }
3227+
3228+ var configs []OAuthClientConfig
3229+ for _ , item := range items {
3230+ clientObj , ok := item .(map [string ]interface {})
3231+ if ! ok {
3232+ continue
3233+ }
3234+ metadata , ok := clientObj ["metadata" ].(map [string ]interface {})
3235+ if ! ok {
3236+ continue
3237+ }
3238+ clientName , ok := metadata ["name" ].(string )
3239+ if ! ok {
3240+ continue
3241+ }
3242+ var maxAge , inactivity * int64
3243+ if v , ok := clientObj ["accessTokenMaxAgeSeconds" ].(float64 ); ok {
3244+ val := int64 (v )
3245+ maxAge = & val
3246+ }
3247+ if v , ok := clientObj ["accessTokenInactivityTimeoutSeconds" ].(float64 ); ok {
3248+ val := int64 (v )
3249+ inactivity = & val
3250+ }
3251+ configs = append (configs , OAuthClientConfig {
3252+ Name : clientName ,
3253+ AccessTokenMaxAgeSeconds : maxAge ,
3254+ AccessTokenInactivityTimeoutSeconds : inactivity ,
3255+ })
3256+ }
3257+ return configs , nil
3258+ }
3259+
3260+ // UpdateOAuthClientConfigs patches every OAuth client with the given token settings and verifies the patch.
3261+ func (f * Framework ) UpdateOAuthClientConfigs (maxAgeSeconds , inactivityTimeoutSeconds int64 ) error {
3262+ return updateOAuthClientConfigs (f , maxAgeSeconds , inactivityTimeoutSeconds )
3263+ }
3264+
3265+ func updateOAuthClientConfigs (f * Framework , maxAgeSeconds , inactivityTimeoutSeconds int64 ) error {
3266+ oauthConfig := * f .KubeConfig
3267+ oauthConfig .GroupVersion = & schema.GroupVersion {Group : "oauth.openshift.io" , Version : "v1" }
3268+ oauthConfig .APIPath = "/apis"
3269+ oauthConfig .NegotiatedSerializer = serializer .NewCodecFactory (runtime .NewScheme ())
3270+ restClient , err := rest .RESTClientFor (& oauthConfig )
3271+ if err != nil {
3272+ return fmt .Errorf ("failed to create REST client for oauth: %w" , err )
3273+ }
3274+
3275+ oauthClientListBytes , err := restClient .Get ().
3276+ Resource ("oauthclients" ).
3277+ Do (context .TODO ()).
3278+ Raw ()
3279+ if err != nil {
3280+ return fmt .Errorf ("failed to list oauthclients: %w" , err )
3281+ }
3282+
3283+ var oauthClientList map [string ]interface {}
3284+ if err := json .Unmarshal (oauthClientListBytes , & oauthClientList ); err != nil {
3285+ return fmt .Errorf ("failed to parse oauthclient list response: %w" , err )
3286+ }
3287+
3288+ items , ok := oauthClientList ["items" ].([]interface {})
3289+ if ! ok {
3290+ return fmt .Errorf ("failed to extract items from oauthclient list" )
3291+ }
3292+
3293+ for _ , item := range items {
3294+ clientObj , ok := item .(map [string ]interface {})
3295+ if ! ok {
3296+ continue
3297+ }
3298+ metadata , ok := clientObj ["metadata" ].(map [string ]interface {})
3299+ if ! ok {
3300+ continue
3301+ }
3302+ clientName , ok := metadata ["name" ].(string )
3303+ if ! ok {
3304+ continue
3305+ }
3306+
3307+ patch := []byte (fmt .Sprintf (`{"accessTokenMaxAgeSeconds":%d,"accessTokenInactivityTimeoutSeconds":%d}` , maxAgeSeconds , inactivityTimeoutSeconds ))
3308+ err = restClient .Patch (types .MergePatchType ).
3309+ Resource ("oauthclients" ).
3310+ Name (clientName ).
3311+ Body (patch ).
3312+ Do (context .TODO ()).
3313+ Error ()
3314+ if err != nil {
3315+ return fmt .Errorf ("failed to patch oauthclient %s: %w" , clientName , err )
3316+ }
3317+
3318+ patchedClientBytes , err := restClient .Get ().
3319+ Resource ("oauthclients" ).
3320+ Name (clientName ).
3321+ Do (context .TODO ()).
3322+ Raw ()
3323+ if err != nil {
3324+ return fmt .Errorf ("failed to get patched oauthclient %s: %w" , clientName , err )
3325+ }
3326+ var patchedObj map [string ]interface {}
3327+ if err := json .Unmarshal (patchedClientBytes , & patchedObj ); err != nil {
3328+ return fmt .Errorf ("failed to parse patched oauthclient %s: %w" , clientName , err )
3329+ }
3330+ gotMaxAge , ok := patchedObj ["accessTokenMaxAgeSeconds" ].(float64 )
3331+ if ! ok || int64 (gotMaxAge ) != maxAgeSeconds {
3332+ return fmt .Errorf ("expected accessTokenMaxAgeSeconds to be %d for oauthclient %s, got %v" , maxAgeSeconds , clientName , gotMaxAge )
3333+ }
3334+ gotInactivity , ok := patchedObj ["accessTokenInactivityTimeoutSeconds" ].(float64 )
3335+ if ! ok || int64 (gotInactivity ) != inactivityTimeoutSeconds {
3336+ return fmt .Errorf ("expected accessTokenInactivityTimeoutSeconds to be %d for oauthclient %s, got %v" , inactivityTimeoutSeconds , clientName , gotInactivity )
3337+ }
3338+ log .Printf ("successfully patched oauthclient %s with accessTokenMaxAgeSeconds=%d and accessTokenInactivityTimeoutSeconds=%d" , clientName , maxAgeSeconds , inactivityTimeoutSeconds )
3339+ }
3340+ return nil
3341+ }
3342+
3343+ // RestoreOAuthClientConfigs patches each OAuth client back to the given configs (e.g. from GetOAuthClientConfigs).
3344+ func (f * Framework ) RestoreOAuthClientConfigs (configs []OAuthClientConfig ) error {
3345+ if len (configs ) == 0 {
3346+ return nil
3347+ }
3348+ oauthConfig := * f .KubeConfig
3349+ oauthConfig .GroupVersion = & schema.GroupVersion {Group : "oauth.openshift.io" , Version : "v1" }
3350+ oauthConfig .APIPath = "/apis"
3351+ oauthConfig .NegotiatedSerializer = serializer .NewCodecFactory (runtime .NewScheme ())
3352+ restClient , err := rest .RESTClientFor (& oauthConfig )
3353+ if err != nil {
3354+ return fmt .Errorf ("failed to create REST client for oauth: %w" , err )
3355+ }
3356+
3357+ for _ , cfg := range configs {
3358+ patchObj := make (map [string ]interface {})
3359+ if cfg .AccessTokenMaxAgeSeconds != nil {
3360+ patchObj ["accessTokenMaxAgeSeconds" ] = * cfg .AccessTokenMaxAgeSeconds
3361+ } else {
3362+ patchObj ["accessTokenMaxAgeSeconds" ] = nil
3363+ }
3364+ if cfg .AccessTokenInactivityTimeoutSeconds != nil {
3365+ patchObj ["accessTokenInactivityTimeoutSeconds" ] = * cfg .AccessTokenInactivityTimeoutSeconds
3366+ } else {
3367+ patchObj ["accessTokenInactivityTimeoutSeconds" ] = nil
3368+ }
3369+ patch , err := json .Marshal (patchObj )
3370+ if err != nil {
3371+ return fmt .Errorf ("marshal restore patch for oauthclient %s: %w" , cfg .Name , err )
3372+ }
3373+ err = restClient .Patch (types .MergePatchType ).
3374+ Resource ("oauthclients" ).
3375+ Name (cfg .Name ).
3376+ Body (patch ).
3377+ Do (context .TODO ()).
3378+ Error ()
3379+ if err != nil {
3380+ return fmt .Errorf ("failed to restore oauthclient %s: %w" , cfg .Name , err )
3381+ }
3382+ log .Printf ("restored oauthclient %s (maxAge=%v, inactivity=%v)" ,
3383+ cfg .Name , cfg .AccessTokenMaxAgeSeconds , cfg .AccessTokenInactivityTimeoutSeconds )
3384+ }
3385+ return nil
3386+ }
3387+
0 commit comments