Skip to content

Commit f152699

Browse files
committed
(feat) validate that secret is valid api token
Validate that a secret associated with a service account is a valid api token. This basically a copy of [1]. I ran into an issue while vendoring this package. [1] https://github.com/kubernetes/kubernetes/blob/master/pkg/serviceaccount/util.go
1 parent 1ff6eff commit f152699

File tree

7 files changed

+132
-40
lines changed

7 files changed

+132
-40
lines changed

cmd/olm/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ func main() {
162162
olm.WithResyncPeriod(*wakeupInterval),
163163
olm.WithExternalClient(crClient),
164164
olm.WithOperatorClient(opClient),
165+
olm.WithRestConfig(config),
165166
)
166167
if err != nil {
167168
log.Fatalf("error configuring operator: %s", err.Error())

pkg/controller/operators/olm/config.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/sirupsen/logrus"
88
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
99
utilclock "k8s.io/apimachinery/pkg/util/clock"
10+
"k8s.io/client-go/rest"
1011

1112
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/internalversion"
1213
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
@@ -30,6 +31,7 @@ type operatorConfig struct {
3031
strategyResolver install.StrategyResolverInterface
3132
apiReconciler resolver.APIIntersectionReconciler
3233
apiLabeler labeler.Labeler
34+
restConfig *rest.Config
3335
}
3436

3537
func (o *operatorConfig) apply(options []OperatorOption) {
@@ -67,6 +69,8 @@ func (o *operatorConfig) validate() (err error) {
6769
err = newInvalidConfigError("api reconciler", "must not be nil")
6870
case o.apiLabeler == nil:
6971
err = newInvalidConfigError("api labeler", "must not be nil")
72+
case o.restConfig == nil:
73+
err = newInvalidConfigError("rest config", "must not be nil")
7074
}
7175

7276
return
@@ -150,3 +154,9 @@ func WithAPILabeler(apiLabeler labeler.Labeler) OperatorOption {
150154
config.apiLabeler = apiLabeler
151155
}
152156
}
157+
158+
func WithRestConfig(restConfig *rest.Config) OperatorOption {
159+
return func(config *operatorConfig) {
160+
config.restConfig = restConfig
161+
}
162+
}

pkg/controller/operators/olm/operator.go

Lines changed: 54 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ import (
1919
utilerrors "k8s.io/apimachinery/pkg/util/errors"
2020
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
2121
"k8s.io/client-go/informers"
22+
k8sscheme "k8s.io/client-go/kubernetes/scheme"
2223
"k8s.io/client-go/tools/cache"
2324
"k8s.io/client-go/tools/record"
2425
"k8s.io/client-go/util/workqueue"
25-
k8sscheme "k8s.io/client-go/kubernetes/scheme"
2626
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
2727
kagg "k8s.io/kube-aggregator/pkg/client/informers/externalversions"
2828

@@ -55,25 +55,27 @@ var timeNow = func() metav1.Time { return metav1.NewTime(time.Now().UTC()) }
5555
type Operator struct {
5656
queueinformer.Operator
5757

58-
clock utilclock.Clock
59-
logger *logrus.Logger
60-
opClient operatorclient.ClientInterface
61-
client versioned.Interface
62-
lister operatorlister.OperatorLister
63-
ogQueueSet *queueinformer.ResourceQueueSet
64-
csvQueueSet *queueinformer.ResourceQueueSet
65-
csvCopyQueueSet *queueinformer.ResourceQueueSet
66-
csvGCQueueSet *queueinformer.ResourceQueueSet
67-
apiServiceQueue workqueue.RateLimitingInterface
68-
csvIndexers map[string]cache.Indexer
69-
recorder record.EventRecorder
70-
resolver install.StrategyResolverInterface
71-
apiReconciler resolver.APIIntersectionReconciler
72-
apiLabeler labeler.Labeler
73-
csvSetGenerator csvutility.SetGenerator
74-
csvReplaceFinder csvutility.ReplaceFinder
75-
csvNotification csvutility.WatchNotification
76-
serviceAccountSyncer *scoped.UserDefinedServiceAccountSyncer
58+
clock utilclock.Clock
59+
logger *logrus.Logger
60+
opClient operatorclient.ClientInterface
61+
client versioned.Interface
62+
lister operatorlister.OperatorLister
63+
ogQueueSet *queueinformer.ResourceQueueSet
64+
csvQueueSet *queueinformer.ResourceQueueSet
65+
csvCopyQueueSet *queueinformer.ResourceQueueSet
66+
csvGCQueueSet *queueinformer.ResourceQueueSet
67+
apiServiceQueue workqueue.RateLimitingInterface
68+
csvIndexers map[string]cache.Indexer
69+
recorder record.EventRecorder
70+
resolver install.StrategyResolverInterface
71+
apiReconciler resolver.APIIntersectionReconciler
72+
apiLabeler labeler.Labeler
73+
csvSetGenerator csvutility.SetGenerator
74+
csvReplaceFinder csvutility.ReplaceFinder
75+
csvNotification csvutility.WatchNotification
76+
serviceAccountSyncer *scoped.UserDefinedServiceAccountSyncer
77+
clientAttenuator *scoped.ClientAttenuator
78+
serviceAccountQuerier *scoped.UserDefinedServiceAccountQuerier
7779
}
7880

7981
func NewOperator(ctx context.Context, options ...OperatorOption) (*Operator, error) {
@@ -106,25 +108,27 @@ func newOperatorWithConfig(ctx context.Context, config *operatorConfig) (*Operat
106108
}
107109

108110
op := &Operator{
109-
Operator: queueOperator,
110-
clock: config.clock,
111-
logger: config.logger,
112-
opClient: config.operatorClient,
113-
client: config.externalClient,
114-
ogQueueSet: queueinformer.NewEmptyResourceQueueSet(),
115-
csvQueueSet: queueinformer.NewEmptyResourceQueueSet(),
116-
csvCopyQueueSet: queueinformer.NewEmptyResourceQueueSet(),
117-
csvGCQueueSet: queueinformer.NewEmptyResourceQueueSet(),
118-
apiServiceQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "apiservice"),
119-
resolver: config.strategyResolver,
120-
apiReconciler: config.apiReconciler,
121-
lister: lister,
122-
recorder: eventRecorder,
123-
apiLabeler: config.apiLabeler,
124-
csvIndexers: map[string]cache.Indexer{},
125-
csvSetGenerator: csvutility.NewSetGenerator(config.logger, lister),
126-
csvReplaceFinder: csvutility.NewReplaceFinder(config.logger, config.externalClient),
127-
serviceAccountSyncer: scoped.NewUserDefinedServiceAccountSyncer(config.logger, scheme, config.operatorClient, config.externalClient),
111+
Operator: queueOperator,
112+
clock: config.clock,
113+
logger: config.logger,
114+
opClient: config.operatorClient,
115+
client: config.externalClient,
116+
ogQueueSet: queueinformer.NewEmptyResourceQueueSet(),
117+
csvQueueSet: queueinformer.NewEmptyResourceQueueSet(),
118+
csvCopyQueueSet: queueinformer.NewEmptyResourceQueueSet(),
119+
csvGCQueueSet: queueinformer.NewEmptyResourceQueueSet(),
120+
apiServiceQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "apiservice"),
121+
resolver: config.strategyResolver,
122+
apiReconciler: config.apiReconciler,
123+
lister: lister,
124+
recorder: eventRecorder,
125+
apiLabeler: config.apiLabeler,
126+
csvIndexers: map[string]cache.Indexer{},
127+
csvSetGenerator: csvutility.NewSetGenerator(config.logger, lister),
128+
csvReplaceFinder: csvutility.NewReplaceFinder(config.logger, config.externalClient),
129+
serviceAccountSyncer: scoped.NewUserDefinedServiceAccountSyncer(config.logger, scheme, config.operatorClient, config.externalClient),
130+
clientAttenuator: scoped.NewClientAttenuator(config.logger, config.restConfig, config.operatorClient, config.externalClient),
131+
serviceAccountQuerier: scoped.NewUserDefinedServiceAccountQuerier(config.logger, config.externalClient),
128132
}
129133

130134
// Set up syncing for namespace-scoped resources
@@ -1326,8 +1330,18 @@ func (a *Operator) parseStrategiesAndUpdateStatus(csv *v1alpha1.ClusterServiceVe
13261330
}
13271331
}
13281332

1333+
// If an admin has specified a service account to the operator group
1334+
// associated with the namespace then we should use a scoped client that is
1335+
// bound to the service account.
1336+
querierFunc := a.serviceAccountQuerier.NamespaceQuerier(csv.GetNamespace())
1337+
kubeclient, err := a.clientAttenuator.AttenuateOperatorClient(querierFunc)
1338+
if err != nil {
1339+
a.logger.Errorf("failed to get a client for operator deployment- %v", err)
1340+
return nil, nil
1341+
}
1342+
13291343
strName := strategy.GetStrategyName()
1330-
installer := a.resolver.InstallerForStrategy(strName, a.opClient, a.lister, csv, csv.Annotations, previousStrategy)
1344+
installer := a.resolver.InstallerForStrategy(strName, kubeclient, a.lister, csv, csv.Annotations, previousStrategy)
13311345
return installer, strategy
13321346
}
13331347

pkg/controller/operators/olm/operator_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
"k8s.io/client-go/tools/cache"
4141
"k8s.io/client-go/tools/record"
4242
k8sscheme "k8s.io/client-go/kubernetes/scheme"
43+
"k8s.io/client-go/rest"
4344
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
4445
apiregistrationfake "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake"
4546

@@ -260,6 +261,7 @@ func NewFakeOperator(ctx context.Context, options ...fakeOperatorOption) (*Opera
260261
strategyResolver: &install.StrategyResolver{},
261262
apiReconciler: resolver.APIIntersectionReconcileFunc(resolver.ReconcileAPIIntersection),
262263
apiLabeler: labeler.Func(resolver.LabelSetsFor),
264+
restConfig: &rest.Config{},
263265
},
264266
recorder: &record.FakeRecorder{},
265267
// default expected namespaces

pkg/lib/scoped/attenuator.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,36 @@ func (s *ClientAttenuator) AttenuateClient(querier ServiceAccountQuerierFunc) (k
9191

9292
return
9393
}
94+
95+
// AttenuateOperatorClient returns a scoped operator client instance based on the
96+
// service account returned by the querier specified.
97+
func (s *ClientAttenuator) AttenuateOperatorClient(querier ServiceAccountQuerierFunc) (kubeclient operatorclient.ClientInterface, err error) {
98+
if querier == nil {
99+
err = errQuerierNotSpecified
100+
return
101+
}
102+
103+
reference, err := querier()
104+
if err != nil {
105+
return
106+
}
107+
108+
if reference == nil {
109+
// No service account/token has been provided. Return the default client(s).
110+
kubeclient = s.kubeclient
111+
return
112+
}
113+
114+
token, err := s.retriever.Retrieve(reference)
115+
if err != nil {
116+
return
117+
}
118+
119+
// Create client(s) bound to the user defined service account.
120+
kubeclient, err = s.factory.NewOperatorClient(token)
121+
if err != nil {
122+
return
123+
}
124+
125+
return
126+
}

pkg/lib/scoped/token_retriever.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ func getAPISecret(logger *logrus.Entry, kubeclient operatorclient.ClientInterfac
6262
break
6363
}
6464

65+
// Validate that this is a token for API access.
66+
if !IsServiceAccountToken(secret, sa) {
67+
logger.Warnf("skipping secret %s - %v", ref.Name, getErr)
68+
continue
69+
}
70+
6571
// The first eligible secret that has an API access token is returned.
6672
APISecret = secret
6773
break

pkg/lib/scoped/util.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package scoped
2+
3+
import (
4+
v1 "k8s.io/api/core/v1"
5+
)
6+
7+
// IsServiceAccountToken returns true if the secret is a valid api token for the service account
8+
// This has been copied from https://github.com/kubernetes/kubernetes/blob/master/pkg/serviceaccount/util.go
9+
func IsServiceAccountToken(secret *v1.Secret, sa *v1.ServiceAccount) bool {
10+
if secret.Type != v1.SecretTypeServiceAccountToken {
11+
return false
12+
}
13+
14+
name := secret.Annotations[v1.ServiceAccountNameKey]
15+
uid := secret.Annotations[v1.ServiceAccountUIDKey]
16+
if name != sa.Name {
17+
// Name must match
18+
return false
19+
}
20+
if len(uid) > 0 && uid != string(sa.UID) {
21+
// If UID is specified, it must match
22+
return false
23+
}
24+
25+
return true
26+
}

0 commit comments

Comments
 (0)