Skip to content

Commit 96f77ae

Browse files
authored
feat: add BoundServiceAccountToken trigger authentication type (kedacore#6272)
Signed-off-by: Max Cao <[email protected]>
1 parent dbd8b52 commit 96f77ae

File tree

18 files changed

+741
-36
lines changed

18 files changed

+741
-36
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio
6464
### New
6565

6666
- **General**: Add Fallback option `behavior` for dynamic fallback calculation ([#6450](https://github.com/kedacore/keda/issues/6450))
67+
- **General**: Add support for time-bound Kubernetes ServiceAccount tokens as a source for TriggerAuthentication ([#6136](https://github.com/kedacore/keda/issues/6136))
6768
- **General**: Enable OpenSSF Scorecard to enhance security practices across the project ([#5913](https://github.com/kedacore/keda/issues/5913))
6869
- **General**: Introduce new NSQ scaler ([#3281](https://github.com/kedacore/keda/issues/3281))
6970
- **General**: Operator flag to control patching of webhook resources certificates ([#6184](https://github.com/kedacore/keda/issues/6184))

apis/keda/v1alpha1/triggerauthentication_types.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ type TriggerAuthenticationSpec struct {
9595

9696
// +optional
9797
AwsSecretManager *AwsSecretManager `json:"awsSecretManager,omitempty"`
98+
99+
// +optional
100+
BoundServiceAccountToken []BoundServiceAccountToken `json:"boundServiceAccountToken,omitempty"`
98101
}
99102

100103
// TriggerAuthenticationStatus defines the observed state of TriggerAuthentication
@@ -380,6 +383,11 @@ type AwsSecretManagerSecret struct {
380383
SecretKey string `json:"secretKey,omitempty"`
381384
}
382385

386+
type BoundServiceAccountToken struct {
387+
Parameter string `json:"parameter"`
388+
ServiceAccountName string `json:"serviceAccountName"`
389+
}
390+
383391
func init() {
384392
SchemeBuilder.Register(&ClusterTriggerAuthentication{}, &ClusterTriggerAuthenticationList{})
385393
SchemeBuilder.Register(&TriggerAuthentication{}, &TriggerAuthenticationList{})

apis/keda/v1alpha1/zz_generated.deepcopy.go

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/operator/main.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import (
4646
"github.com/kedacore/keda/v2/pkg/k8s"
4747
"github.com/kedacore/keda/v2/pkg/metricscollector"
4848
"github.com/kedacore/keda/v2/pkg/metricsservice"
49+
"github.com/kedacore/keda/v2/pkg/scalers/authentication"
4950
"github.com/kedacore/keda/v2/pkg/scaling"
5051
kedautil "github.com/kedacore/keda/v2/pkg/util"
5152
//+kubebuilder:scaffold:imports
@@ -201,6 +202,12 @@ func main() {
201202
os.Exit(1)
202203
}
203204

205+
_, err = kedautil.GetBoundServiceAccountTokenExpiry()
206+
if err != nil {
207+
setupLog.Error(err, "invalid "+kedautil.BoundServiceAccountTokenExpiryEnvVar)
208+
os.Exit(1)
209+
}
210+
204211
globalHTTPTimeout := time.Duration(globalHTTPTimeoutMS) * time.Millisecond
205212
eventRecorder := mgr.GetEventRecorderFor("keda-operator")
206213

@@ -225,8 +232,13 @@ func main() {
225232
os.Exit(1)
226233
}
227234

228-
scaledHandler := scaling.NewScaleHandler(mgr.GetClient(), scaleClient, mgr.GetScheme(), globalHTTPTimeout, eventRecorder, secretInformer.Lister())
229-
eventEmitter := eventemitter.NewEventEmitter(mgr.GetClient(), eventRecorder, k8sClusterName, secretInformer.Lister())
235+
authClientSet := &authentication.AuthClientSet{
236+
CoreV1Interface: kubeClientset.CoreV1(),
237+
SecretLister: secretInformer.Lister(),
238+
}
239+
240+
scaledHandler := scaling.NewScaleHandler(mgr.GetClient(), scaleClient, mgr.GetScheme(), globalHTTPTimeout, eventRecorder, authClientSet)
241+
eventEmitter := eventemitter.NewEventEmitter(mgr.GetClient(), eventRecorder, k8sClusterName, authClientSet)
230242

231243
if err = (&kedacontrollers.ScaledObjectReconciler{
232244
Client: mgr.GetClient(),
@@ -245,8 +257,7 @@ func main() {
245257
Scheme: mgr.GetScheme(),
246258
GlobalHTTPTimeout: globalHTTPTimeout,
247259
EventEmitter: eventEmitter,
248-
SecretsLister: secretInformer.Lister(),
249-
SecretsSynced: secretInformer.Informer().HasSynced,
260+
AuthClientSet: authClientSet,
250261
}).SetupWithManager(mgr, controller.Options{
251262
MaxConcurrentReconciles: scaledJobMaxReconciles,
252263
}); err != nil {

config/crd/bases/keda.sh_clustertriggerauthentications.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,18 @@ spec:
304304
- secrets
305305
- vaultUri
306306
type: object
307+
boundServiceAccountToken:
308+
items:
309+
properties:
310+
parameter:
311+
type: string
312+
serviceAccountName:
313+
type: string
314+
required:
315+
- parameter
316+
- serviceAccountName
317+
type: object
318+
type: array
307319
configMapTargetRef:
308320
items:
309321
description: AuthConfigMapTargetRef is used to authenticate using

config/crd/bases/keda.sh_triggerauthentications.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,18 @@ spec:
303303
- secrets
304304
- vaultUri
305305
type: object
306+
boundServiceAccountToken:
307+
items:
308+
properties:
309+
parameter:
310+
type: string
311+
serviceAccountName:
312+
type: string
313+
required:
314+
- parameter
315+
- serviceAccountName
316+
type: object
317+
type: array
306318
configMapTargetRef:
307319
items:
308320
description: AuthConfigMapTargetRef is used to authenticate using

controllers/keda/scaledjob_controller.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import (
2929
"k8s.io/apimachinery/pkg/api/errors"
3030
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3131
"k8s.io/apimachinery/pkg/runtime"
32-
corev1listers "k8s.io/client-go/listers/core/v1"
3332
"k8s.io/client-go/tools/cache"
3433
ctrl "sigs.k8s.io/controller-runtime"
3534
"sigs.k8s.io/controller-runtime/pkg/builder"
@@ -45,6 +44,7 @@ import (
4544
"github.com/kedacore/keda/v2/pkg/eventemitter"
4645
"github.com/kedacore/keda/v2/pkg/eventreason"
4746
"github.com/kedacore/keda/v2/pkg/metricscollector"
47+
"github.com/kedacore/keda/v2/pkg/scalers/authentication"
4848
"github.com/kedacore/keda/v2/pkg/scaling"
4949
kedastatus "github.com/kedacore/keda/v2/pkg/status"
5050
"github.com/kedacore/keda/v2/pkg/util"
@@ -59,11 +59,10 @@ type ScaledJobReconciler struct {
5959
Scheme *runtime.Scheme
6060
GlobalHTTPTimeout time.Duration
6161
EventEmitter eventemitter.EventHandler
62+
AuthClientSet *authentication.AuthClientSet
6263

6364
scaledJobGenerations *sync.Map
6465
scaleHandler scaling.ScaleHandler
65-
SecretsLister corev1listers.SecretLister
66-
SecretsSynced cache.InformerSynced
6766
}
6867

6968
type scaledJobMetricsData struct {
@@ -83,7 +82,7 @@ func init() {
8382

8483
// SetupWithManager initializes the ScaledJobReconciler instance and starts a new controller managed by the passed Manager instance.
8584
func (r *ScaledJobReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error {
86-
r.scaleHandler = scaling.NewScaleHandler(mgr.GetClient(), nil, mgr.GetScheme(), r.GlobalHTTPTimeout, mgr.GetEventRecorderFor("scale-handler"), r.SecretsLister)
85+
r.scaleHandler = scaling.NewScaleHandler(mgr.GetClient(), nil, mgr.GetScheme(), r.GlobalHTTPTimeout, mgr.GetEventRecorderFor("scale-handler"), r.AuthClientSet)
8786
r.scaledJobGenerations = &sync.Map{}
8887
return ctrl.NewControllerManagedBy(mgr).
8988
WithOptions(options).

controllers/keda/suite_test.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1"
3737
"github.com/kedacore/keda/v2/pkg/eventemitter"
3838
"github.com/kedacore/keda/v2/pkg/k8s"
39+
"github.com/kedacore/keda/v2/pkg/scalers/authentication"
3940
"github.com/kedacore/keda/v2/pkg/scaling"
4041
//+kubebuilder:scaffold:imports
4142
)
@@ -91,19 +92,22 @@ var _ = BeforeSuite(func() {
9192
scaleClient, _, err := k8s.InitScaleClient(k8sManager)
9293
Expect(err).ToNot(HaveOccurred())
9394

95+
authClientSet := &authentication.AuthClientSet{}
96+
9497
err = (&ScaledObjectReconciler{
9598
Client: k8sManager.GetClient(),
9699
Scheme: k8sManager.GetScheme(),
97-
ScaleHandler: scaling.NewScaleHandler(k8sManager.GetClient(), scaleClient, k8sManager.GetScheme(), time.Duration(10), k8sManager.GetEventRecorderFor("keda-operator"), nil),
100+
ScaleHandler: scaling.NewScaleHandler(k8sManager.GetClient(), scaleClient, k8sManager.GetScheme(), time.Duration(10), k8sManager.GetEventRecorderFor("keda-operator"), authClientSet),
98101
ScaleClient: scaleClient,
99102
EventEmitter: eventemitter.NewEventEmitter(k8sManager.GetClient(), k8sManager.GetEventRecorderFor("keda-operator"), "kubernetes-default", nil),
100103
}).SetupWithManager(k8sManager, controller.Options{})
101104
Expect(err).ToNot(HaveOccurred())
102105

103106
err = (&ScaledJobReconciler{
104-
Client: k8sManager.GetClient(),
105-
Scheme: k8sManager.GetScheme(),
106-
EventEmitter: eventemitter.NewEventEmitter(k8sManager.GetClient(), k8sManager.GetEventRecorderFor("keda-operator"), "kubernetes-default", nil),
107+
Client: k8sManager.GetClient(),
108+
Scheme: k8sManager.GetScheme(),
109+
EventEmitter: eventemitter.NewEventEmitter(k8sManager.GetClient(), k8sManager.GetEventRecorderFor("keda-operator"), "kubernetes-default", nil),
110+
AuthClientSet: authClientSet,
107111
}).SetupWithManager(k8sManager, controller.Options{})
108112
Expect(err).ToNot(HaveOccurred())
109113

pkg/eventemitter/eventemitter.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ import (
3636
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3737
"k8s.io/apimachinery/pkg/runtime"
3838
"k8s.io/apimachinery/pkg/types"
39-
corev1listers "k8s.io/client-go/listers/core/v1"
4039
"k8s.io/client-go/tools/record"
4140
"sigs.k8s.io/controller-runtime/pkg/client"
4241
logf "sigs.k8s.io/controller-runtime/pkg/log"
4342

4443
eventingv1alpha1 "github.com/kedacore/keda/v2/apis/eventing/v1alpha1"
4544
"github.com/kedacore/keda/v2/pkg/eventemitter/eventdata"
4645
"github.com/kedacore/keda/v2/pkg/metricscollector"
46+
"github.com/kedacore/keda/v2/pkg/scalers/authentication"
4747
"github.com/kedacore/keda/v2/pkg/scaling/resolver"
4848
kedastatus "github.com/kedacore/keda/v2/pkg/status"
4949
)
@@ -66,7 +66,7 @@ type EventEmitter struct {
6666
eventFilterCacheLock *sync.RWMutex
6767
eventLoopContexts *sync.Map
6868
cloudEventProcessingChan chan eventdata.EventData
69-
secretsLister corev1listers.SecretLister
69+
authClientSet *authentication.AuthClientSet
7070
}
7171

7272
// EventHandler defines the behavior for EventEmitter clients
@@ -96,7 +96,7 @@ const (
9696
)
9797

9898
// NewEventEmitter creates a new EventEmitter
99-
func NewEventEmitter(client client.Client, recorder record.EventRecorder, clusterName string, secretsLister corev1listers.SecretLister) EventHandler {
99+
func NewEventEmitter(client client.Client, recorder record.EventRecorder, clusterName string, authClientSet *authentication.AuthClientSet) EventHandler {
100100
return &EventEmitter{
101101
log: logf.Log.WithName("event_emitter"),
102102
client: client,
@@ -108,7 +108,7 @@ func NewEventEmitter(client client.Client, recorder record.EventRecorder, cluste
108108
eventFilterCacheLock: &sync.RWMutex{},
109109
eventLoopContexts: &sync.Map{},
110110
cloudEventProcessingChan: make(chan eventdata.EventData, maxChannelBuffer),
111-
secretsLister: secretsLister,
111+
authClientSet: authClientSet,
112112
}
113113
}
114114

@@ -188,7 +188,7 @@ func (e *EventEmitter) createEventHandlers(ctx context.Context, cloudEventSource
188188
}
189189

190190
// Resolve auth related
191-
authParams, podIdentity, err := resolver.ResolveAuthRefAndPodIdentity(ctx, e.client, e.log, spec.AuthenticationRef, nil, cloudEventSourceI.GetNamespace(), e.secretsLister)
191+
authParams, podIdentity, err := resolver.ResolveAuthRefAndPodIdentity(ctx, e.client, e.log, spec.AuthenticationRef, nil, cloudEventSourceI.GetNamespace(), e.authClientSet)
192192
if err != nil {
193193
e.log.Error(err, "error resolving auth params", "cloudEventSource", cloudEventSourceI)
194194
return
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Generated from these commands and then edited:
2+
//
3+
// mockgen -source=k8s.io/client-go/kubernetes/typed/core/v1/serviceaccount.go -imports=k8s.io/client-go/kubernetes/typed/core/v1/core_client.go
4+
// mockgen k8s.io/client-go/kubernetes/typed/core/v1 CoreV1Interface
5+
//
6+
// Package mock_v1 is a generated GoMock package from various generated sources and edited to remove unnecessary code.
7+
//
8+
9+
package mock_v1 //nolint:revive,stylecheck
10+
11+
import (
12+
context "context"
13+
reflect "reflect"
14+
15+
gomock "go.uber.org/mock/gomock"
16+
v10 "k8s.io/api/authentication/v1"
17+
v12 "k8s.io/apimachinery/pkg/apis/meta/v1"
18+
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
19+
)
20+
21+
// MockCoreV1Interface is a mock of CoreV1Interface interface.
22+
type MockCoreV1Interface struct {
23+
v1.CoreV1Interface
24+
mockServiceAccount *MockServiceAccountInterface
25+
ctrl *gomock.Controller
26+
recorder *MockCoreV1InterfaceMockRecorder
27+
}
28+
29+
// MockCoreV1InterfaceMockRecorder is the mock recorder for MockCoreV1Interface.
30+
type MockCoreV1InterfaceMockRecorder struct {
31+
mock *MockCoreV1Interface
32+
}
33+
34+
// NewMockCoreV1Interface creates a new mock instance.
35+
func NewMockCoreV1Interface(ctrl *gomock.Controller) *MockCoreV1Interface {
36+
mock := &MockCoreV1Interface{ctrl: ctrl}
37+
mock.mockServiceAccount = NewMockServiceAccountInterface(ctrl)
38+
mock.recorder = &MockCoreV1InterfaceMockRecorder{mock}
39+
return mock
40+
}
41+
42+
// GetServiceAccountInterface returns the mock for ServiceAccountInterface.
43+
func (m *MockCoreV1Interface) GetServiceAccountInterface() *MockServiceAccountInterface {
44+
return m.mockServiceAccount
45+
}
46+
47+
// EXPECT returns an object that allows the caller to indicate expected use.
48+
func (m *MockCoreV1Interface) EXPECT() *MockCoreV1InterfaceMockRecorder {
49+
return m.recorder
50+
}
51+
52+
// ServiceAccounts mocks base method.
53+
func (m *MockCoreV1Interface) ServiceAccounts(_ string) v1.ServiceAccountInterface {
54+
return m.mockServiceAccount
55+
}
56+
57+
// MockServiceAccountInterface is a mock of ServiceAccountInterface interface.
58+
type MockServiceAccountInterface struct {
59+
v1.ServiceAccountInterface
60+
ctrl *gomock.Controller
61+
recorder *MockServiceAccountInterfaceMockRecorder
62+
}
63+
64+
// MockServiceAccountInterfaceMockRecorder is the mock recorder for MockServiceAccountInterface.
65+
type MockServiceAccountInterfaceMockRecorder struct {
66+
mock *MockServiceAccountInterface
67+
}
68+
69+
// NewMockServiceAccountInterface creates a new mock instance.
70+
func NewMockServiceAccountInterface(ctrl *gomock.Controller) *MockServiceAccountInterface {
71+
mock := &MockServiceAccountInterface{ctrl: ctrl}
72+
mock.recorder = &MockServiceAccountInterfaceMockRecorder{mock}
73+
return mock
74+
}
75+
76+
// EXPECT returns an object that allows the caller to indicate expected use.
77+
func (m *MockServiceAccountInterface) EXPECT() *MockServiceAccountInterfaceMockRecorder {
78+
return m.recorder
79+
}
80+
81+
// CreateToken mocks base method.
82+
func (m *MockServiceAccountInterface) CreateToken(ctx context.Context, serviceAccountName string, tokenRequest *v10.TokenRequest, opts v12.CreateOptions) (*v10.TokenRequest, error) {
83+
m.ctrl.T.Helper()
84+
ret := m.ctrl.Call(m, "CreateToken", ctx, serviceAccountName, tokenRequest, opts)
85+
ret0, _ := ret[0].(*v10.TokenRequest)
86+
ret1, _ := ret[1].(error)
87+
return ret0, ret1
88+
}
89+
90+
// CreateToken indicates an expected call of CreateToken.
91+
func (mr *MockServiceAccountInterfaceMockRecorder) CreateToken(ctx, serviceAccountName, tokenRequest, opts any) *gomock.Call {
92+
mr.mock.ctrl.T.Helper()
93+
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateToken", reflect.TypeOf((*MockServiceAccountInterface)(nil).CreateToken), ctx, serviceAccountName, tokenRequest, opts)
94+
}

0 commit comments

Comments
 (0)