Skip to content

Commit 4bc8e45

Browse files
Add test to test tokenrules pass when configuring oauthclients
1 parent 15f8f95 commit 4bc8e45

File tree

2 files changed

+300
-0
lines changed

2 files changed

+300
-0
lines changed

tests/e2e/framework/common.go

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+

tests/e2e/serial/main_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2487,5 +2487,99 @@ func TestStrictNodeScanConfiguration(t *testing.T) {
24872487
t.Fatalf("suite left PENDING state (expected to remain PENDING for 30s): phase=%s", suite.Status.Phase)
24882488
}
24892489
time.Sleep(framework.RetryInterval)
2490+
}
24902491
}
2492+
2493+
func TestTokenRulesPassOauthClientsConfigurable(t *testing.T) {
2494+
f := framework.Global
2495+
suiteName := framework.GetObjNameFromTest(t)
2496+
tp := &compv1alpha1.TailoredProfile{
2497+
ObjectMeta: metav1.ObjectMeta{
2498+
Name: suiteName,
2499+
Namespace: f.OperatorNamespace,
2500+
},
2501+
Spec: compv1alpha1.TailoredProfileSpec{
2502+
Title: "oauthRules",
2503+
Description: "oauthRules",
2504+
EnableRules: []compv1alpha1.RuleReferenceSpec{
2505+
{
2506+
Name: "ocp4-oauth-or-oauthclient-token-maxage",
2507+
Rationale: "To be tested",
2508+
},
2509+
{
2510+
Name: "ocp4-oauth-or-oauthclient-inactivity-timeout",
2511+
Rationale: "To be tested",
2512+
},
2513+
},
2514+
SetValues: []compv1alpha1.VariableValueSpec{
2515+
{
2516+
Name: "ocp4-var-oauth-token-maxage",
2517+
Value: "28800",
2518+
Rationale: "Set token max age to 28800 seconds",
2519+
},
2520+
},
2521+
},
2522+
}
2523+
createTPErr := f.Client.Create(context.TODO(), tp, nil)
2524+
if createTPErr != nil {
2525+
t.Fatal(createTPErr)
2526+
}
2527+
defer f.Client.Delete(context.TODO(), tp)
2528+
2529+
ssb := &compv1alpha1.ScanSettingBinding{
2530+
ObjectMeta: metav1.ObjectMeta{
2531+
Name: suiteName,
2532+
Namespace: f.OperatorNamespace,
2533+
},
2534+
Profiles: []compv1alpha1.NamedObjectReference{
2535+
{
2536+
APIGroup: "compliance.openshift.io/v1alpha1",
2537+
Kind: "TailoredProfile",
2538+
Name: suiteName,
2539+
},
2540+
},
2541+
SettingsRef: &compv1alpha1.NamedObjectReference{
2542+
APIGroup: "compliance.openshift.io/v1alpha1",
2543+
Kind: "ScanSetting",
2544+
Name: "default",
2545+
},
2546+
}
2547+
err := f.Client.Create(context.TODO(), ssb, nil)
2548+
if err != nil {
2549+
t.Fatal(err)
2550+
}
2551+
defer f.Client.Delete(context.TODO(), ssb)
2552+
2553+
// Wait for initial scan to complete and verify non-compliant
2554+
if err := f.WaitForSuiteScansStatus(f.OperatorNamespace, suiteName, compv1alpha1.PhaseDone, compv1alpha1.ResultNonCompliant); err != nil {
2555+
t.Fatal(err)
2556+
}
2557+
2558+
originalConfigs, err := f.GetOAuthClientConfigs()
2559+
if err != nil {
2560+
t.Fatalf("failed to get oauth client configs: %s", err)
2561+
}
2562+
if err := f.UpdateOAuthClientConfigs(28800, 600); err != nil {
2563+
t.Fatalf("failed to update oauth client configs: %s", err)
2564+
}
2565+
defer func() {
2566+
if restoreErr := f.RestoreOAuthClientConfigs(originalConfigs); restoreErr != nil {
2567+
t.Logf("failed to restore oauth client configs: %v", restoreErr)
2568+
}
2569+
}()
2570+
2571+
// Re-run the suite
2572+
if err := f.RescanSuite(suiteName, f.OperatorNamespace); err != nil {
2573+
t.Fatalf("failed to rescan suite: %s", err)
2574+
}
2575+
2576+
// Wait for scans to restart
2577+
if err := f.WaitForSuiteScansStatus(f.OperatorNamespace, suiteName, compv1alpha1.PhaseRunning, compv1alpha1.ResultNotAvailable); err != nil {
2578+
t.Fatal(err)
2579+
}
2580+
2581+
// Wait for scans to complete and verify compliant
2582+
if err := f.WaitForSuiteScansStatus(f.OperatorNamespace, suiteName, compv1alpha1.PhaseDone, compv1alpha1.ResultCompliant); err != nil {
2583+
t.Fatal(err)
2584+
}
24912585
}

0 commit comments

Comments
 (0)