Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add BoundServiceAccountToken trigger authentication type #6272

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio

### New

- **General**: Add support for time-bound Kubernetes ServiceAccount tokens as a source for TriggerAuthentication ([#6136](https://github.com/kedacore/keda/issues/6136))
- **General**: Enable OpenSSF Scorecard to enhance security practices across the project ([#5913](https://github.com/kedacore/keda/issues/5913))
- **General**: Introduce new NSQ scaler ([#3281](https://github.com/kedacore/keda/issues/3281))
- **General**: Operator flag to control patching of webhook resources certificates ([#6184](https://github.com/kedacore/keda/issues/6184))
Expand Down
8 changes: 8 additions & 0 deletions apis/keda/v1alpha1/triggerauthentication_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ type TriggerAuthenticationSpec struct {

// +optional
AwsSecretManager *AwsSecretManager `json:"awsSecretManager,omitempty"`

// +optional
BoundServiceAccountToken []BoundServiceAccountToken `json:"boundServiceAccountToken,omitempty"`
}

// TriggerAuthenticationStatus defines the observed state of TriggerAuthentication
Expand Down Expand Up @@ -378,6 +381,11 @@ type AwsSecretManagerSecret struct {
VersionStage string `json:"versionStage,omitempty"`
}

type BoundServiceAccountToken struct {
Parameter string `json:"parameter"`
ServiceAccountName string `json:"serviceAccountName"`
}

func init() {
SchemeBuilder.Register(&ClusterTriggerAuthentication{}, &ClusterTriggerAuthenticationList{})
SchemeBuilder.Register(&TriggerAuthentication{}, &TriggerAuthenticationList{})
Expand Down
20 changes: 20 additions & 0 deletions apis/keda/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 15 additions & 4 deletions cmd/operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import (
"github.com/kedacore/keda/v2/pkg/k8s"
"github.com/kedacore/keda/v2/pkg/metricscollector"
"github.com/kedacore/keda/v2/pkg/metricsservice"
"github.com/kedacore/keda/v2/pkg/scalers/authentication"
"github.com/kedacore/keda/v2/pkg/scaling"
kedautil "github.com/kedacore/keda/v2/pkg/util"
//+kubebuilder:scaffold:imports
Expand Down Expand Up @@ -201,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")

Expand All @@ -225,8 +232,13 @@ func main() {
os.Exit(1)
}

scaledHandler := scaling.NewScaleHandler(mgr.GetClient(), scaleClient, mgr.GetScheme(), globalHTTPTimeout, eventRecorder, secretInformer.Lister())
eventEmitter := eventemitter.NewEventEmitter(mgr.GetClient(), eventRecorder, k8sClusterName, secretInformer.Lister())
authClientSet := &authentication.AuthClientSet{
CoreV1Interface: kubeClientset.CoreV1(),
SecretLister: secretInformer.Lister(),
}

scaledHandler := scaling.NewScaleHandler(mgr.GetClient(), scaleClient, mgr.GetScheme(), globalHTTPTimeout, eventRecorder, authClientSet)
eventEmitter := eventemitter.NewEventEmitter(mgr.GetClient(), eventRecorder, k8sClusterName, authClientSet)

if err = (&kedacontrollers.ScaledObjectReconciler{
Client: mgr.GetClient(),
Expand All @@ -245,8 +257,7 @@ func main() {
Scheme: mgr.GetScheme(),
GlobalHTTPTimeout: globalHTTPTimeout,
EventEmitter: eventEmitter,
SecretsLister: secretInformer.Lister(),
SecretsSynced: secretInformer.Informer().HasSynced,
AuthClientSet: authClientSet,
}).SetupWithManager(mgr, controller.Options{
MaxConcurrentReconciles: scaledJobMaxReconciles,
}); err != nil {
Expand Down
12 changes: 12 additions & 0 deletions config/crd/bases/keda.sh_clustertriggerauthentications.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,18 @@ spec:
- secrets
- vaultUri
type: object
boundServiceAccountToken:
items:
properties:
parameter:
type: string
serviceAccountName:
type: string
required:
- parameter
- serviceAccountName
type: object
type: array
configMapTargetRef:
items:
description: AuthConfigMapTargetRef is used to authenticate using
Expand Down
12 changes: 12 additions & 0 deletions config/crd/bases/keda.sh_triggerauthentications.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,18 @@ spec:
- secrets
- vaultUri
type: object
boundServiceAccountToken:
items:
properties:
parameter:
type: string
serviceAccountName:
type: string
required:
- parameter
- serviceAccountName
type: object
type: array
configMapTargetRef:
items:
description: AuthConfigMapTargetRef is used to authenticate using
Expand Down
7 changes: 3 additions & 4 deletions controllers/keda/scaledjob_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
Expand All @@ -45,6 +44,7 @@ import (
"github.com/kedacore/keda/v2/pkg/eventemitter"
"github.com/kedacore/keda/v2/pkg/eventreason"
"github.com/kedacore/keda/v2/pkg/metricscollector"
"github.com/kedacore/keda/v2/pkg/scalers/authentication"
"github.com/kedacore/keda/v2/pkg/scaling"
kedastatus "github.com/kedacore/keda/v2/pkg/status"
"github.com/kedacore/keda/v2/pkg/util"
Expand All @@ -59,11 +59,10 @@ type ScaledJobReconciler struct {
Scheme *runtime.Scheme
GlobalHTTPTimeout time.Duration
EventEmitter eventemitter.EventHandler
AuthClientSet *authentication.AuthClientSet

scaledJobGenerations *sync.Map
scaleHandler scaling.ScaleHandler
SecretsLister corev1listers.SecretLister
SecretsSynced cache.InformerSynced
}

type scaledJobMetricsData struct {
Expand All @@ -83,7 +82,7 @@ func init() {

// SetupWithManager initializes the ScaledJobReconciler instance and starts a new controller managed by the passed Manager instance.
func (r *ScaledJobReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error {
r.scaleHandler = scaling.NewScaleHandler(mgr.GetClient(), nil, mgr.GetScheme(), r.GlobalHTTPTimeout, mgr.GetEventRecorderFor("scale-handler"), r.SecretsLister)
r.scaleHandler = scaling.NewScaleHandler(mgr.GetClient(), nil, mgr.GetScheme(), r.GlobalHTTPTimeout, mgr.GetEventRecorderFor("scale-handler"), r.AuthClientSet)
r.scaledJobGenerations = &sync.Map{}
return ctrl.NewControllerManagedBy(mgr).
WithOptions(options).
Expand Down
12 changes: 8 additions & 4 deletions controllers/keda/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1"
"github.com/kedacore/keda/v2/pkg/eventemitter"
"github.com/kedacore/keda/v2/pkg/k8s"
"github.com/kedacore/keda/v2/pkg/scalers/authentication"
"github.com/kedacore/keda/v2/pkg/scaling"
//+kubebuilder:scaffold:imports
)
Expand Down Expand Up @@ -91,19 +92,22 @@ var _ = BeforeSuite(func() {
scaleClient, _, err := k8s.InitScaleClient(k8sManager)
Expect(err).ToNot(HaveOccurred())

authClientSet := &authentication.AuthClientSet{}

err = (&ScaledObjectReconciler{
Client: k8sManager.GetClient(),
Scheme: k8sManager.GetScheme(),
ScaleHandler: scaling.NewScaleHandler(k8sManager.GetClient(), scaleClient, k8sManager.GetScheme(), time.Duration(10), k8sManager.GetEventRecorderFor("keda-operator"), nil),
ScaleHandler: scaling.NewScaleHandler(k8sManager.GetClient(), scaleClient, k8sManager.GetScheme(), time.Duration(10), k8sManager.GetEventRecorderFor("keda-operator"), authClientSet),
ScaleClient: scaleClient,
EventEmitter: eventemitter.NewEventEmitter(k8sManager.GetClient(), k8sManager.GetEventRecorderFor("keda-operator"), "kubernetes-default", nil),
}).SetupWithManager(k8sManager, controller.Options{})
Expect(err).ToNot(HaveOccurred())

err = (&ScaledJobReconciler{
Client: k8sManager.GetClient(),
Scheme: k8sManager.GetScheme(),
EventEmitter: eventemitter.NewEventEmitter(k8sManager.GetClient(), k8sManager.GetEventRecorderFor("keda-operator"), "kubernetes-default", nil),
Client: k8sManager.GetClient(),
Scheme: k8sManager.GetScheme(),
EventEmitter: eventemitter.NewEventEmitter(k8sManager.GetClient(), k8sManager.GetEventRecorderFor("keda-operator"), "kubernetes-default", nil),
AuthClientSet: authClientSet,
}).SetupWithManager(k8sManager, controller.Options{})
Expect(err).ToNot(HaveOccurred())

Expand Down
10 changes: 5 additions & 5 deletions pkg/eventemitter/eventemitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/record"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"

eventingv1alpha1 "github.com/kedacore/keda/v2/apis/eventing/v1alpha1"
"github.com/kedacore/keda/v2/pkg/eventemitter/eventdata"
"github.com/kedacore/keda/v2/pkg/metricscollector"
"github.com/kedacore/keda/v2/pkg/scalers/authentication"
"github.com/kedacore/keda/v2/pkg/scaling/resolver"
kedastatus "github.com/kedacore/keda/v2/pkg/status"
)
Expand All @@ -66,7 +66,7 @@ type EventEmitter struct {
eventFilterCacheLock *sync.RWMutex
eventLoopContexts *sync.Map
cloudEventProcessingChan chan eventdata.EventData
secretsLister corev1listers.SecretLister
authClientSet *authentication.AuthClientSet
}

// EventHandler defines the behavior for EventEmitter clients
Expand Down Expand Up @@ -96,7 +96,7 @@ const (
)

// NewEventEmitter creates a new EventEmitter
func NewEventEmitter(client client.Client, recorder record.EventRecorder, clusterName string, secretsLister corev1listers.SecretLister) EventHandler {
func NewEventEmitter(client client.Client, recorder record.EventRecorder, clusterName string, authClientSet *authentication.AuthClientSet) EventHandler {
return &EventEmitter{
log: logf.Log.WithName("event_emitter"),
client: client,
Expand All @@ -108,7 +108,7 @@ func NewEventEmitter(client client.Client, recorder record.EventRecorder, cluste
eventFilterCacheLock: &sync.RWMutex{},
eventLoopContexts: &sync.Map{},
cloudEventProcessingChan: make(chan eventdata.EventData, maxChannelBuffer),
secretsLister: secretsLister,
authClientSet: authClientSet,
}
}

Expand Down Expand Up @@ -188,7 +188,7 @@ func (e *EventEmitter) createEventHandlers(ctx context.Context, cloudEventSource
}

// Resolve auth related
authParams, podIdentity, err := resolver.ResolveAuthRefAndPodIdentity(ctx, e.client, e.log, spec.AuthenticationRef, nil, cloudEventSourceI.GetNamespace(), e.secretsLister)
authParams, podIdentity, err := resolver.ResolveAuthRefAndPodIdentity(ctx, e.client, e.log, spec.AuthenticationRef, nil, cloudEventSourceI.GetNamespace(), e.authClientSet)
if err != nil {
e.log.Error(err, "error resolving auth params", "cloudEventSource", cloudEventSourceI)
return
Expand Down
94 changes: 94 additions & 0 deletions pkg/mock/mock_serviceaccounts/mock_interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Generated from these commands and then edited:
//
// mockgen -source=k8s.io/client-go/kubernetes/typed/core/v1/serviceaccount.go -imports=k8s.io/client-go/kubernetes/typed/core/v1/core_client.go
// mockgen k8s.io/client-go/kubernetes/typed/core/v1 CoreV1Interface
//
// Package mock_v1 is a generated GoMock package from various generated sources and edited to remove unnecessary code.
//

package mock_v1 //nolint:revive,stylecheck

import (
context "context"
reflect "reflect"

gomock "go.uber.org/mock/gomock"
v10 "k8s.io/api/authentication/v1"
v12 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
)

// MockCoreV1Interface is a mock of CoreV1Interface interface.
type MockCoreV1Interface struct {
v1.CoreV1Interface
mockServiceAccount *MockServiceAccountInterface
ctrl *gomock.Controller
recorder *MockCoreV1InterfaceMockRecorder
}

// MockCoreV1InterfaceMockRecorder is the mock recorder for MockCoreV1Interface.
type MockCoreV1InterfaceMockRecorder struct {
mock *MockCoreV1Interface
}

// NewMockCoreV1Interface creates a new mock instance.
func NewMockCoreV1Interface(ctrl *gomock.Controller) *MockCoreV1Interface {
mock := &MockCoreV1Interface{ctrl: ctrl}
mock.mockServiceAccount = NewMockServiceAccountInterface(ctrl)
mock.recorder = &MockCoreV1InterfaceMockRecorder{mock}
return mock
}

// GetServiceAccountInterface returns the mock for ServiceAccountInterface.
func (m *MockCoreV1Interface) GetServiceAccountInterface() *MockServiceAccountInterface {
return m.mockServiceAccount
}

// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCoreV1Interface) EXPECT() *MockCoreV1InterfaceMockRecorder {
return m.recorder
}

// ServiceAccounts mocks base method.
func (m *MockCoreV1Interface) ServiceAccounts(_ string) v1.ServiceAccountInterface {
return m.mockServiceAccount
}

// MockServiceAccountInterface is a mock of ServiceAccountInterface interface.
type MockServiceAccountInterface struct {
v1.ServiceAccountInterface
ctrl *gomock.Controller
recorder *MockServiceAccountInterfaceMockRecorder
}

// MockServiceAccountInterfaceMockRecorder is the mock recorder for MockServiceAccountInterface.
type MockServiceAccountInterfaceMockRecorder struct {
mock *MockServiceAccountInterface
}

// NewMockServiceAccountInterface creates a new mock instance.
func NewMockServiceAccountInterface(ctrl *gomock.Controller) *MockServiceAccountInterface {
mock := &MockServiceAccountInterface{ctrl: ctrl}
mock.recorder = &MockServiceAccountInterfaceMockRecorder{mock}
return mock
}

// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockServiceAccountInterface) EXPECT() *MockServiceAccountInterfaceMockRecorder {
return m.recorder
}

// CreateToken mocks base method.
func (m *MockServiceAccountInterface) CreateToken(ctx context.Context, serviceAccountName string, tokenRequest *v10.TokenRequest, opts v12.CreateOptions) (*v10.TokenRequest, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateToken", ctx, serviceAccountName, tokenRequest, opts)
ret0, _ := ret[0].(*v10.TokenRequest)
ret1, _ := ret[1].(error)
return ret0, ret1
}

// CreateToken indicates an expected call of CreateToken.
func (mr *MockServiceAccountInterfaceMockRecorder) CreateToken(ctx, serviceAccountName, tokenRequest, opts any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateToken", reflect.TypeOf((*MockServiceAccountInterface)(nil).CreateToken), ctx, serviceAccountName, tokenRequest, opts)
}
7 changes: 7 additions & 0 deletions pkg/scalers/authentication/authentication_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,17 @@ import (
libs "github.com/dysnix/predictkube-libs/external/configs"
"github.com/dysnix/predictkube-libs/external/http_transport"
pConfig "github.com/prometheus/common/config"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
corev1listers "k8s.io/client-go/listers/core/v1"

kedautil "github.com/kedacore/keda/v2/pkg/util"
)

type AuthClientSet struct {
corev1client.CoreV1Interface
corev1listers.SecretLister
}

const (
AuthModesKey = "authModes"
)
Expand Down
Loading
Loading