From 91dc6886737a3b47f98b5d56f796d6c87a5686a3 Mon Sep 17 00:00:00 2001 From: Max Cao Date: Mon, 9 Dec 2024 11:58:25 -0800 Subject: [PATCH] Allow bound service account token expiry to be configurable through env vars Signed-off-by: Max Cao --- cmd/operator/main.go | 11 +++++++--- .../authentication/authentication_helpers.go | 2 -- pkg/scaling/resolver/scale_resolvers.go | 20 ++++++++++-------- pkg/scaling/resolver/scale_resolvers_test.go | 7 ++----- pkg/util/env_resolver.go | 21 +++++++++++++++++++ 5 files changed, 42 insertions(+), 19 deletions(-) diff --git a/cmd/operator/main.go b/cmd/operator/main.go index 389ccca8317..55e9638b6f0 100644 --- a/cmd/operator/main.go +++ b/cmd/operator/main.go @@ -202,6 +202,12 @@ func main() { os.Exit(1) } + _, err = kedautil.GetBoundServiceAccountTokenExpiry() + if err != nil { + setupLog.Error(err, "invalid "+kedautil.BoundServiceAccountTokenExpiryEnvVar) + os.Exit(1) + } + globalHTTPTimeout := time.Duration(globalHTTPTimeoutMS) * time.Millisecond eventRecorder := mgr.GetEventRecorderFor("keda-operator") @@ -227,9 +233,8 @@ func main() { } authClientSet := &authentication.AuthClientSet{ - TokenReviewInterface: kubeClientset.AuthenticationV1().TokenReviews(), - CoreV1Interface: kubeClientset.CoreV1(), - SecretLister: secretInformer.Lister(), + CoreV1Interface: kubeClientset.CoreV1(), + SecretLister: secretInformer.Lister(), } scaledHandler := scaling.NewScaleHandler(mgr.GetClient(), scaleClient, mgr.GetScheme(), globalHTTPTimeout, eventRecorder, authClientSet) diff --git a/pkg/scalers/authentication/authentication_helpers.go b/pkg/scalers/authentication/authentication_helpers.go index f8ac658e01c..1b89c048590 100644 --- a/pkg/scalers/authentication/authentication_helpers.go +++ b/pkg/scalers/authentication/authentication_helpers.go @@ -13,7 +13,6 @@ import ( libs "github.com/dysnix/predictkube-libs/external/configs" "github.com/dysnix/predictkube-libs/external/http_transport" pConfig "github.com/prometheus/common/config" - authenticationv1client "k8s.io/client-go/kubernetes/typed/authentication/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" corev1listers "k8s.io/client-go/listers/core/v1" @@ -21,7 +20,6 @@ import ( ) type AuthClientSet struct { - authenticationv1client.TokenReviewInterface corev1client.CoreV1Interface corev1listers.SecretLister } diff --git a/pkg/scaling/resolver/scale_resolvers.go b/pkg/scaling/resolver/scale_resolvers.go index 2473f4701eb..96133d5b1ad 100644 --- a/pkg/scaling/resolver/scale_resolvers.go +++ b/pkg/scaling/resolver/scale_resolvers.go @@ -23,7 +23,6 @@ import ( "strconv" "strings" - "github.com/aws/smithy-go/ptr" "github.com/go-logr/logr" appsv1 "k8s.io/api/apps/v1" authenticationv1 "k8s.io/api/authentication/v1" @@ -32,6 +31,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" corev1listers "k8s.io/client-go/listers/core/v1" + "k8s.io/utils/ptr" "knative.dev/pkg/apis/duck" duckv1 "knative.dev/pkg/apis/duck/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -52,9 +52,10 @@ const ( ) var ( - kedaNamespace, _ = util.GetClusterObjectNamespace() - restrictSecretAccess = util.GetRestrictSecretAccess() - log = logf.Log.WithName("scale_resolvers") + kedaNamespace, _ = util.GetClusterObjectNamespace() + restrictSecretAccess = util.GetRestrictSecretAccess() + boundServiceAccountTokenExpiry, _ = util.GetBoundServiceAccountTokenExpiry() + log = logf.Log.WithName("scale_resolvers") ) // isSecretAccessRestricted returns whether secret access need to be restricted in KEDA namespace @@ -620,11 +621,12 @@ func resolveBoundServiceAccountToken(ctx context.Context, client client.Client, logger.Error(err, "error trying to get service account from namespace", "ServiceAccount.Namespace", namespace, "ServiceAccount.Name", serviceAccountName) return "" } - return generateToken(ctx, serviceAccountName, namespace, acs) + return generateBoundServiceAccountToken(ctx, serviceAccountName, namespace, acs) } -func generateToken(ctx context.Context, serviceAccountName, namespace string, acs *authentication.AuthClientSet) string { - expirationSeconds := ptr.Int64(3600) // We default the token expiry to 1 hour +// generateBoundServiceAccountToken creates a Kubernetes token for a namespaced service account with a runtime-configurable expiration time and returns the token string. +func generateBoundServiceAccountToken(ctx context.Context, serviceAccountName, namespace string, acs *authentication.AuthClientSet) string { + expirationSeconds := ptr.To[int64](int64(boundServiceAccountTokenExpiry.Seconds())) token, err := acs.CoreV1Interface.ServiceAccounts(namespace).CreateToken( ctx, serviceAccountName, @@ -636,10 +638,10 @@ func generateToken(ctx context.Context, serviceAccountName, namespace string, ac metav1.CreateOptions{}, ) if err != nil { - log.Error(err, "error trying to create token for service account", "ServiceAccount.Name", serviceAccountName) + log.V(1).Error(err, "error trying to create bound service account token for service account", "ServiceAccount.Name", serviceAccountName) return "" } - log.Info("Service account token created successfully", "ServiceAccount.Name", serviceAccountName) + log.V(1).Info("Bound service account token created successfully", "ServiceAccount.Name", serviceAccountName) return token.Status.Token } diff --git a/pkg/scaling/resolver/scale_resolvers_test.go b/pkg/scaling/resolver/scale_resolvers_test.go index 6609de65013..4485d6554e0 100644 --- a/pkg/scaling/resolver/scale_resolvers_test.go +++ b/pkg/scaling/resolver/scale_resolvers_test.go @@ -28,7 +28,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/scheme" - authenticationv1client "k8s.io/client-go/kubernetes/typed/authentication/v1" corev1listers "k8s.io/client-go/listers/core/v1" "sigs.k8s.io/controller-runtime/pkg/client/fake" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -708,7 +707,6 @@ func TestResolveAuthRef(t *testing.T) { } ctrl := gomock.NewController(t) var secretsLister corev1listers.SecretLister - var tokenReviewInterface authenticationv1client.TokenReviewInterface mockCoreV1Interface := mock_serviceaccounts.NewMockCoreV1Interface(ctrl) mockServiceAccountInterface := mockCoreV1Interface.GetServiceAccountInterface() tokenRequest := &authv1.TokenRequest{ @@ -731,9 +729,8 @@ func TestResolveAuthRef(t *testing.T) { test.podSpec, namespace, &authentication.AuthClientSet{ - SecretLister: secretsLister, - CoreV1Interface: mockCoreV1Interface, - TokenReviewInterface: tokenReviewInterface, + SecretLister: secretsLister, + CoreV1Interface: mockCoreV1Interface, }, ) diff --git a/pkg/util/env_resolver.go b/pkg/util/env_resolver.go index b8e9cfa8763..1dcb3598c4c 100644 --- a/pkg/util/env_resolver.go +++ b/pkg/util/env_resolver.go @@ -17,12 +17,16 @@ limitations under the License. package util import ( + "fmt" "os" "strconv" "time" + + "k8s.io/utils/ptr" ) const RestrictSecretAccessEnvVar = "KEDA_RESTRICT_SECRET_ACCESS" +const BoundServiceAccountTokenExpiryEnvVar = "KEDA_BOUND_SERVICE_ACCOUNT_TOKEN_EXPIRY" var clusterObjectNamespaceCache *string @@ -90,3 +94,20 @@ func GetPodNamespace() string { func GetRestrictSecretAccess() string { return os.Getenv(RestrictSecretAccessEnvVar) } + +// GetBoundServiceAccountTokenExpiry retrieves the value of the environment variable of KEDA_BOUND_SERVICE_ACCOUNT_TOKEN_EXPIRY +func GetBoundServiceAccountTokenExpiry() (*time.Duration, error) { + expiry, err := ResolveOsEnvDuration(BoundServiceAccountTokenExpiryEnvVar) + if err != nil { + return nil, err + } + if expiry == nil { + return ptr.To[time.Duration](time.Hour), nil // if blank, default to 1 hour + + } + if *expiry < time.Hour || *expiry > 6*time.Hour { + return nil, fmt.Errorf("invalid value for %s: %s, must be between 1h and 6h", BoundServiceAccountTokenExpiryEnvVar, expiry.String()) // Must be between 1 hour and 6 hours + + } + return expiry, nil +}