@@ -10,8 +10,12 @@ import (
10
10
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11
11
"k8s.io/apimachinery/pkg/runtime"
12
12
"k8s.io/apimachinery/pkg/runtime/schema"
13
+ "k8s.io/apimachinery/pkg/util/sets"
13
14
apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
14
15
apirequest "k8s.io/apiserver/pkg/endpoints/request"
16
+ corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
17
+ clientv1 "k8s.io/client-go/pkg/api/v1"
18
+ "k8s.io/client-go/tools/record"
15
19
kapi "k8s.io/kubernetes/pkg/api"
16
20
kcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
17
21
"k8s.io/kubernetes/pkg/serviceaccount"
@@ -21,7 +25,6 @@ import (
21
25
oauthapi "github.com/openshift/origin/pkg/oauth/apis/oauth"
22
26
"github.com/openshift/origin/pkg/oauth/registry/oauthclient"
23
27
routeapi "github.com/openshift/origin/pkg/route/apis/route"
24
- "k8s.io/apimachinery/pkg/util/sets"
25
28
)
26
29
27
30
const (
@@ -47,8 +50,8 @@ var modelPrefixes = []string{
47
50
// namesToObjMapperFunc is linked to a given GroupKind.
48
51
// Based on the namespace and names provided, it builds a map of resource name to redirect URIs.
49
52
// The redirect URIs represent the default values as specified by the resource.
50
- // These values can be overridden by user specified data.
51
- type namesToObjMapperFunc func (namespace string , names sets.String ) map [string ]redirectURIList
53
+ // These values can be overridden by user specified data. Errors returned are informative and non-fatal.
54
+ type namesToObjMapperFunc func (namespace string , names sets.String ) ( map [string ]redirectURIList , [] error )
52
55
53
56
var emptyGroupKind = schema.GroupKind {} // Used with static redirect URIs
54
57
var routeGroupKind = routeapi .SchemeGroupVersion .WithKind (routeKind ).GroupKind ()
@@ -58,9 +61,10 @@ var legacyRouteGroupKind = routeapi.LegacySchemeGroupVersion.WithKind(routeKind)
58
61
// var ingressGroupKind = routeapi.SchemeGroupVersion.WithKind(IngressKind).GroupKind()
59
62
60
63
type saOAuthClientAdapter struct {
61
- saClient kcoreclient.ServiceAccountsGetter
62
- secretClient kcoreclient.SecretsGetter
63
- routeClient osclient.RoutesNamespacer
64
+ saClient kcoreclient.ServiceAccountsGetter
65
+ secretClient kcoreclient.SecretsGetter
66
+ eventRecorder record.EventRecorder
67
+ routeClient osclient.RoutesNamespacer
64
68
// TODO add ingress support
65
69
//ingressClient ??
66
70
@@ -186,11 +190,36 @@ func (uri *redirectURI) merge(m *model) {
186
190
187
191
var _ oauthclient.Getter = & saOAuthClientAdapter {}
188
192
189
- func NewServiceAccountOAuthClientGetter (saClient kcoreclient.ServiceAccountsGetter , secretClient kcoreclient.SecretsGetter , routeClient osclient.RoutesNamespacer , delegate oauthclient.Getter , grantMethod oauthapi.GrantHandlerType ) oauthclient.Getter {
190
- return & saOAuthClientAdapter {saClient : saClient , secretClient : secretClient , routeClient : routeClient , delegate : delegate , grantMethod : grantMethod , decoder : kapi .Codecs .UniversalDecoder ()}
193
+ func NewServiceAccountOAuthClientGetter (saClient kcoreclient.ServiceAccountsGetter , secretClient kcoreclient.SecretsGetter , eventClient corev1.EventInterface , routeClient osclient.RoutesNamespacer , delegate oauthclient.Getter , grantMethod oauthapi.GrantHandlerType ) oauthclient.Getter {
194
+ opts := record .NewDefaultEventCorrelatorOptions ()
195
+ // Limit the OAuth events to 1 new event per minute.
196
+ opts .SpamBurst = 1
197
+ opts .SpamQPS = 1. / 60.
198
+
199
+ eventBroadcaster := record .NewBroadcaster ()
200
+ eventBroadcaster .StartRecordingToSinkWithOptions (& corev1.EventSinkImpl {Interface : eventClient }, opts )
201
+ recorder := eventBroadcaster .NewRecorder (kapi .Scheme , clientv1.EventSource {Component : "service-account-oauth-client-getter" })
202
+ return & saOAuthClientAdapter {
203
+ saClient : saClient ,
204
+ secretClient : secretClient ,
205
+ eventRecorder : recorder ,
206
+ routeClient : routeClient ,
207
+ delegate : delegate ,
208
+ grantMethod : grantMethod ,
209
+ decoder : kapi .Codecs .UniversalDecoder (),
210
+ }
211
+ }
212
+
213
+ func joinErrors (errors []error ) string {
214
+ msg := []string {}
215
+ for _ , e := range errors {
216
+ msg = append (msg , e .Error ())
217
+ }
218
+ return strings .Join (msg , "," )
191
219
}
192
220
193
221
func (a * saOAuthClientAdapter ) GetClient (ctx apirequest.Context , name string , options * metav1.GetOptions ) (* oauthapi.OAuthClient , error ) {
222
+ var err error
194
223
saNamespace , saName , err := apiserverserviceaccount .SplitUsername (name )
195
224
if err != nil {
196
225
return a .delegate .GetClient (ctx , name , options )
@@ -201,25 +230,44 @@ func (a *saOAuthClientAdapter) GetClient(ctx apirequest.Context, name string, op
201
230
return nil , err
202
231
}
203
232
233
+ var saErrors []error
234
+ var failReason string
235
+ // Create a warning event combining the collected annotation errors upon failure.
236
+ defer func () {
237
+ if err != nil && len (saErrors ) > 0 && len (failReason ) > 0 {
238
+ a .eventRecorder .Eventf (sa , kapi .EventTypeWarning , failReason , "%s" , joinErrors (saErrors ))
239
+ }
240
+ }()
241
+
204
242
redirectURIs := []string {}
205
- if modelsMap := parseModelsMap (sa .Annotations , a .decoder ); len (modelsMap ) > 0 {
206
- if uris := a .extractRedirectURIs (modelsMap , saNamespace ); len (uris ) > 0 {
243
+ modelsMap , saErrors := parseModelsMap (sa .Annotations , a .decoder )
244
+ if len (modelsMap ) > 0 {
245
+ uris , extractErrors := a .extractRedirectURIs (modelsMap , saNamespace )
246
+ if len (uris ) > 0 {
207
247
redirectURIs = append (redirectURIs , uris .extractValidRedirectURIStrings ()... )
208
248
}
249
+ if len (extractErrors ) > 0 {
250
+ saErrors = append (saErrors , extractErrors ... )
251
+ }
209
252
}
210
253
if len (redirectURIs ) == 0 {
211
- return nil , fmt .Errorf (
212
- "%v has no redirectURIs; set %v<some-value>=<redirect> or create a dynamic URI using %v<some-value>=<reference>" ,
254
+ err = fmt .Errorf ("%v has no redirectURIs; set %v<some-value>=<redirect> or create a dynamic URI using %v<some-value>=<reference>" ,
213
255
name , OAuthRedirectModelAnnotationURIPrefix , OAuthRedirectModelAnnotationReferencePrefix ,
214
256
)
257
+ failReason = "NoSAOAuthRedirectURIs"
258
+ saErrors = append (saErrors , err )
259
+ return nil , err
215
260
}
216
261
217
262
tokens , err := a .getServiceAccountTokens (sa )
218
263
if err != nil {
219
264
return nil , err
220
265
}
221
266
if len (tokens ) == 0 {
222
- return nil , fmt .Errorf ("%v has no tokens" , name )
267
+ err = fmt .Errorf ("%v has no tokens" , name )
268
+ failReason = "NoSAOAuthTokens"
269
+ saErrors = append (saErrors , err )
270
+ return nil , err
223
271
}
224
272
225
273
saWantsChallenges , _ := strconv .ParseBool (sa .Annotations [OAuthWantChallengesAnnotationPrefix ])
@@ -242,9 +290,10 @@ func (a *saOAuthClientAdapter) GetClient(ctx apirequest.Context, name string, op
242
290
243
291
// parseModelsMap builds a map of model name to model using a service account's annotations.
244
292
// The model name is only used for building the map (it ties together the uri and reference annotations)
245
- // and serves no functional purpose other than making testing easier.
246
- func parseModelsMap (annotations map [string ]string , decoder runtime.Decoder ) map [string ]model {
293
+ // and serves no functional purpose other than making testing easier. Errors returned are informative and non-fatal.
294
+ func parseModelsMap (annotations map [string ]string , decoder runtime.Decoder ) ( map [string ]model , [] error ) {
247
295
models := map [string ]model {}
296
+ parseErrors := []error {}
248
297
for key , value := range annotations {
249
298
prefix , name , ok := parseModelPrefixName (key )
250
299
if ! ok {
@@ -255,16 +304,20 @@ func parseModelsMap(annotations map[string]string, decoder runtime.Decoder) map[
255
304
case OAuthRedirectModelAnnotationURIPrefix :
256
305
if u , err := url .Parse (value ); err == nil {
257
306
m .updateFromURI (u )
307
+ } else {
308
+ parseErrors = append (parseErrors , err )
258
309
}
259
310
case OAuthRedirectModelAnnotationReferencePrefix :
260
311
r := & oauthapi.OAuthRedirectReference {}
261
312
if err := runtime .DecodeInto (decoder , []byte (value ), r ); err == nil {
262
313
m .updateFromReference (& r .Reference )
314
+ } else {
315
+ parseErrors = append (parseErrors , err )
263
316
}
264
317
}
265
318
models [name ] = m
266
319
}
267
- return models
320
+ return models , parseErrors
268
321
}
269
322
270
323
// parseModelPrefixName determines if the given key is a model prefix.
@@ -279,9 +332,10 @@ func parseModelPrefixName(key string) (string, string, bool) {
279
332
}
280
333
281
334
// extractRedirectURIs builds redirect URIs using the given models and namespace.
282
- // The returned redirect URIs may contain duplicates and invalid entries.
283
- func (a * saOAuthClientAdapter ) extractRedirectURIs (modelsMap map [string ]model , namespace string ) redirectURIList {
335
+ // The returned redirect URIs may contain duplicates and invalid entries. Errors returned are informative and non-fatal.
336
+ func (a * saOAuthClientAdapter ) extractRedirectURIs (modelsMap map [string ]model , namespace string ) ( redirectURIList , [] error ) {
284
337
var data redirectURIList
338
+ routeErrors := []error {}
285
339
groupKindModelListMapper := map [schema.GroupKind ]modelList {} // map of GroupKind to all models belonging to it
286
340
groupKindModelToURI := map [schema.GroupKind ]namesToObjMapperFunc {
287
341
routeGroupKind : a .redirectURIsFromRoutes ,
@@ -305,27 +359,37 @@ func (a *saOAuthClientAdapter) extractRedirectURIs(modelsMap map[string]model, n
305
359
306
360
for gk , models := range groupKindModelListMapper {
307
361
if names := models .getNames (); names .Len () > 0 {
308
- if objMapper := groupKindModelToURI [gk ](namespace , names ); len (objMapper ) > 0 {
362
+ objMapper , errs := groupKindModelToURI [gk ](namespace , names )
363
+ if len (objMapper ) > 0 {
309
364
data = append (data , models .getRedirectURIs (objMapper )... )
310
365
}
366
+ if len (errs ) > 0 {
367
+ routeErrors = append (routeErrors , errs ... )
368
+ }
311
369
}
312
370
}
313
371
314
- return data
372
+ return data , routeErrors
315
373
}
316
374
317
375
// redirectURIsFromRoutes is the namesToObjMapperFunc specific to Routes.
318
376
// Returns a map of route name to redirect URIs that contain the default data as specified by the route's ingresses.
319
- func (a * saOAuthClientAdapter ) redirectURIsFromRoutes (namespace string , osRouteNames sets.String ) map [string ]redirectURIList {
377
+ // Errors returned are informative and non-fatal.
378
+ func (a * saOAuthClientAdapter ) redirectURIsFromRoutes (namespace string , osRouteNames sets.String ) (map [string ]redirectURIList , []error ) {
320
379
var routes []routeapi.Route
380
+ routeErrors := []error {}
321
381
routeInterface := a .routeClient .Routes (namespace )
322
382
if osRouteNames .Len () > 1 {
323
383
if r , err := routeInterface .List (metav1.ListOptions {}); err == nil {
324
384
routes = r .Items
385
+ } else {
386
+ routeErrors = append (routeErrors , err )
325
387
}
326
388
} else {
327
389
if r , err := routeInterface .Get (osRouteNames .List ()[0 ], metav1.GetOptions {}); err == nil {
328
390
routes = append (routes , * r )
391
+ } else {
392
+ routeErrors = append (routeErrors , err )
329
393
}
330
394
}
331
395
routeMap := map [string ]redirectURIList {}
@@ -334,7 +398,7 @@ func (a *saOAuthClientAdapter) redirectURIsFromRoutes(namespace string, osRouteN
334
398
routeMap [route .Name ] = redirectURIsFromRoute (& route )
335
399
}
336
400
}
337
- return routeMap
401
+ return routeMap , routeErrors
338
402
}
339
403
340
404
// redirectURIsFromRoute returns a list of redirect URIs that contain the default data as specified by the given route's ingresses.
0 commit comments