Skip to content

Commit 25b1ed1

Browse files
author
Matt Rogers
committed
Add API events for SA OAuth failures
Collect errors during validation of a service account's OAuth configuration, logging them to a warning event upon a fatal error. Signed-off-by: Matt Rogers <[email protected]>
1 parent b3475a3 commit 25b1ed1

File tree

5 files changed

+174
-40
lines changed

5 files changed

+174
-40
lines changed

pkg/oauth/apiserver/apiserver.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"k8s.io/apimachinery/pkg/runtime/serializer"
99
"k8s.io/apiserver/pkg/registry/rest"
1010
genericapiserver "k8s.io/apiserver/pkg/server"
11+
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
1112
restclient "k8s.io/client-go/rest"
1213
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
1314

@@ -121,8 +122,19 @@ func (c *OAuthAPIServerConfig) newV1RESTStorage() (map[string]rest.Storage, erro
121122
if err != nil {
122123
return nil, err
123124
}
125+
coreV1Client, err := corev1.NewForConfig(c.CoreAPIServerClientConfig)
126+
if err != nil {
127+
return nil, err
128+
}
124129

125-
combinedOAuthClientGetter := saoauth.NewServiceAccountOAuthClientGetter(coreClient, coreClient, routeClient, oauthClient.OAuthClients(), saAccountGrantMethod)
130+
combinedOAuthClientGetter := saoauth.NewServiceAccountOAuthClientGetter(
131+
coreClient,
132+
coreClient,
133+
coreV1Client.Events(""),
134+
routeClient,
135+
oauthClient.OAuthClients(),
136+
saAccountGrantMethod,
137+
)
126138
authorizeTokenStorage, err := authorizetokenetcd.NewREST(c.GenericConfig.RESTOptionsGetter, combinedOAuthClientGetter)
127139
if err != nil {
128140
return nil, fmt.Errorf("error building REST storage: %v", err)

pkg/oauth/apiserver/auth.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,14 @@ func (c *OAuthServerConfig) WithOAuth(handler http.Handler) (http.Handler, error
7979
// pass through all other requests
8080
mux.Handle("/", handler)
8181

82-
combinedOAuthClientGetter := saoauth.NewServiceAccountOAuthClientGetter(c.KubeClient.Core(), c.KubeClient.Core(), c.RouteClient.Route(), c.OAuthClientClient, oauthapi.GrantHandlerType(c.Options.GrantConfig.ServiceAccountMethod))
82+
combinedOAuthClientGetter := saoauth.NewServiceAccountOAuthClientGetter(
83+
c.KubeClient.Core(),
84+
c.KubeClient.Core(),
85+
c.EventsClient,
86+
c.RouteClient.Route(),
87+
c.OAuthClientClient,
88+
oauthapi.GrantHandlerType(c.Options.GrantConfig.ServiceAccountMethod),
89+
)
8390

8491
errorPageHandler, err := c.getErrorHandler()
8592
if err != nil {

pkg/oauth/apiserver/oauth_apiserver.go

+9
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
apirequest "k8s.io/apiserver/pkg/endpoints/request"
1515
genericapiserver "k8s.io/apiserver/pkg/server"
1616
genericfilters "k8s.io/apiserver/pkg/server/filters"
17+
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
1718
"k8s.io/client-go/rest"
1819
kapi "k8s.io/kubernetes/pkg/api"
1920
kclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
@@ -40,6 +41,9 @@ type OAuthServerConfig struct {
4041
// KubeClient is kubeclient with enough permission for the auth API
4142
KubeClient kclientset.Interface
4243

44+
// EventsClient is for creating user events
45+
EventsClient corev1.EventInterface
46+
4347
// RouteClient provides a client for OpenShift routes API.
4448
RouteClient routeclient.Interface
4549

@@ -80,11 +84,16 @@ func NewOAuthServerConfig(oauthConfig configapi.OAuthConfig, userClientConfig *r
8084
if err != nil {
8185
return nil, err
8286
}
87+
eventsClient, err := corev1.NewForConfig(userClientConfig)
88+
if err != nil {
89+
return nil, err
90+
}
8391

8492
ret := &OAuthServerConfig{
8593
GenericConfig: genericConfig,
8694
Options: oauthConfig,
8795
SessionAuth: sessionAuth,
96+
EventsClient: eventsClient.Events(""),
8897
IdentityClient: userClient.Identities(),
8998
UserClient: userClient.Users(),
9099
UserIdentityMappingClient: userClient.UserIdentityMappings(),

pkg/serviceaccounts/oauthclient/oauthclientregistry.go

+76-27
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import (
1010
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1111
"k8s.io/apimachinery/pkg/runtime"
1212
"k8s.io/apimachinery/pkg/runtime/schema"
13+
utilerrors "k8s.io/apimachinery/pkg/util/errors"
14+
"k8s.io/apimachinery/pkg/util/sets"
1315
apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
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"
1419
kapi "k8s.io/kubernetes/pkg/api"
1520
kcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
1621
"k8s.io/kubernetes/pkg/serviceaccount"
@@ -20,7 +25,6 @@ import (
2025
"github.com/openshift/origin/pkg/oauth/registry/oauthclient"
2126
routeapi "github.com/openshift/origin/pkg/route/apis/route"
2227
routeclient "github.com/openshift/origin/pkg/route/generated/internalclientset/typed/route/internalversion"
23-
"k8s.io/apimachinery/pkg/util/sets"
2428
)
2529

2630
const (
@@ -46,8 +50,8 @@ var modelPrefixes = []string{
4650
// namesToObjMapperFunc is linked to a given GroupKind.
4751
// Based on the namespace and names provided, it builds a map of resource name to redirect URIs.
4852
// The redirect URIs represent the default values as specified by the resource.
49-
// These values can be overridden by user specified data.
50-
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)
5155

5256
var emptyGroupKind = schema.GroupKind{} // Used with static redirect URIs
5357
var routeGroupKind = routeapi.SchemeGroupVersion.WithKind(routeKind).GroupKind()
@@ -57,9 +61,10 @@ var legacyRouteGroupKind = routeapi.LegacySchemeGroupVersion.WithKind(routeKind)
5761
// var ingressGroupKind = routeapi.SchemeGroupVersion.WithKind(IngressKind).GroupKind()
5862

5963
type saOAuthClientAdapter struct {
60-
saClient kcoreclient.ServiceAccountsGetter
61-
secretClient kcoreclient.SecretsGetter
62-
routeClient routeclient.RoutesGetter
64+
saClient kcoreclient.ServiceAccountsGetter
65+
secretClient kcoreclient.SecretsGetter
66+
eventRecorder record.EventRecorder
67+
routeClient routeclient.RoutesGetter
6368
// TODO add ingress support
6469
//ingressClient ??
6570

@@ -188,22 +193,27 @@ var _ oauthclient.Getter = &saOAuthClientAdapter{}
188193
func NewServiceAccountOAuthClientGetter(
189194
saClient kcoreclient.ServiceAccountsGetter,
190195
secretClient kcoreclient.SecretsGetter,
196+
eventClient corev1.EventInterface,
191197
routeClient routeclient.RoutesGetter,
192198
delegate oauthclient.Getter,
193199
grantMethod oauthapi.GrantHandlerType,
194200
) oauthclient.Getter {
195-
201+
eventBroadcaster := record.NewBroadcaster()
202+
eventBroadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface: eventClient})
203+
recorder := eventBroadcaster.NewRecorder(kapi.Scheme, clientv1.EventSource{Component: "service-account-oauth-client-getter"})
196204
return &saOAuthClientAdapter{
197-
saClient: saClient,
198-
secretClient: secretClient,
199-
routeClient: routeClient,
200-
delegate: delegate,
201-
grantMethod: grantMethod,
202-
decoder: kapi.Codecs.UniversalDecoder(),
205+
saClient: saClient,
206+
secretClient: secretClient,
207+
eventRecorder: recorder,
208+
routeClient: routeClient,
209+
delegate: delegate,
210+
grantMethod: grantMethod,
211+
decoder: kapi.Codecs.UniversalDecoder(),
203212
}
204213
}
205214

206215
func (a *saOAuthClientAdapter) Get(name string, options metav1.GetOptions) (*oauthapi.OAuthClient, error) {
216+
var err error
207217
saNamespace, saName, err := apiserverserviceaccount.SplitUsername(name)
208218
if err != nil {
209219
return a.delegate.Get(name, options)
@@ -214,25 +224,48 @@ func (a *saOAuthClientAdapter) Get(name string, options metav1.GetOptions) (*oau
214224
return nil, err
215225
}
216226

227+
var saErrors []error
228+
var failReason string
229+
// Create a warning event combining the collected annotation errors upon failure.
230+
defer func() {
231+
if err != nil && len(saErrors) > 0 && len(failReason) > 0 {
232+
a.eventRecorder.Event(sa, kapi.EventTypeWarning, failReason, utilerrors.NewAggregate(saErrors).Error())
233+
}
234+
}()
235+
217236
redirectURIs := []string{}
218-
if modelsMap := parseModelsMap(sa.Annotations, a.decoder); len(modelsMap) > 0 {
219-
if uris := a.extractRedirectURIs(modelsMap, saNamespace); len(uris) > 0 {
237+
modelsMap, errs := parseModelsMap(sa.Annotations, a.decoder)
238+
if len(errs) > 0 {
239+
saErrors = append(saErrors, errs...)
240+
}
241+
242+
if len(modelsMap) > 0 {
243+
uris, extractErrors := a.extractRedirectURIs(modelsMap, saNamespace)
244+
if len(uris) > 0 {
220245
redirectURIs = append(redirectURIs, uris.extractValidRedirectURIStrings()...)
221246
}
247+
if len(extractErrors) > 0 {
248+
saErrors = append(saErrors, extractErrors...)
249+
}
222250
}
223251
if len(redirectURIs) == 0 {
224-
return nil, fmt.Errorf(
225-
"%v has no redirectURIs; set %v<some-value>=<redirect> or create a dynamic URI using %v<some-value>=<reference>",
252+
err = fmt.Errorf("%v has no redirectURIs; set %v<some-value>=<redirect> or create a dynamic URI using %v<some-value>=<reference>",
226253
name, OAuthRedirectModelAnnotationURIPrefix, OAuthRedirectModelAnnotationReferencePrefix,
227254
)
255+
failReason = "NoSAOAuthRedirectURIs"
256+
saErrors = append(saErrors, err)
257+
return nil, err
228258
}
229259

230260
tokens, err := a.getServiceAccountTokens(sa)
231261
if err != nil {
232262
return nil, err
233263
}
234264
if len(tokens) == 0 {
235-
return nil, fmt.Errorf("%v has no tokens", name)
265+
err = fmt.Errorf("%v has no tokens", name)
266+
failReason = "NoSAOAuthTokens"
267+
saErrors = append(saErrors, err)
268+
return nil, err
236269
}
237270

238271
saWantsChallenges, _ := strconv.ParseBool(sa.Annotations[OAuthWantChallengesAnnotationPrefix])
@@ -255,9 +288,10 @@ func (a *saOAuthClientAdapter) Get(name string, options metav1.GetOptions) (*oau
255288

256289
// parseModelsMap builds a map of model name to model using a service account's annotations.
257290
// The model name is only used for building the map (it ties together the uri and reference annotations)
258-
// and serves no functional purpose other than making testing easier.
259-
func parseModelsMap(annotations map[string]string, decoder runtime.Decoder) map[string]model {
291+
// and serves no functional purpose other than making testing easier. Errors returned are informative and non-fatal.
292+
func parseModelsMap(annotations map[string]string, decoder runtime.Decoder) (map[string]model, []error) {
260293
models := map[string]model{}
294+
parseErrors := []error{}
261295
for key, value := range annotations {
262296
prefix, name, ok := parseModelPrefixName(key)
263297
if !ok {
@@ -268,16 +302,20 @@ func parseModelsMap(annotations map[string]string, decoder runtime.Decoder) map[
268302
case OAuthRedirectModelAnnotationURIPrefix:
269303
if u, err := url.Parse(value); err == nil {
270304
m.updateFromURI(u)
305+
} else {
306+
parseErrors = append(parseErrors, err)
271307
}
272308
case OAuthRedirectModelAnnotationReferencePrefix:
273309
r := &oauthapi.OAuthRedirectReference{}
274310
if err := runtime.DecodeInto(decoder, []byte(value), r); err == nil {
275311
m.updateFromReference(&r.Reference)
312+
} else {
313+
parseErrors = append(parseErrors, err)
276314
}
277315
}
278316
models[name] = m
279317
}
280-
return models
318+
return models, parseErrors
281319
}
282320

283321
// parseModelPrefixName determines if the given key is a model prefix.
@@ -292,9 +330,10 @@ func parseModelPrefixName(key string) (string, string, bool) {
292330
}
293331

294332
// extractRedirectURIs builds redirect URIs using the given models and namespace.
295-
// The returned redirect URIs may contain duplicates and invalid entries.
296-
func (a *saOAuthClientAdapter) extractRedirectURIs(modelsMap map[string]model, namespace string) redirectURIList {
333+
// The returned redirect URIs may contain duplicates and invalid entries. Errors returned are informative and non-fatal.
334+
func (a *saOAuthClientAdapter) extractRedirectURIs(modelsMap map[string]model, namespace string) (redirectURIList, []error) {
297335
var data redirectURIList
336+
routeErrors := []error{}
298337
groupKindModelListMapper := map[schema.GroupKind]modelList{} // map of GroupKind to all models belonging to it
299338
groupKindModelToURI := map[schema.GroupKind]namesToObjMapperFunc{
300339
routeGroupKind: a.redirectURIsFromRoutes,
@@ -318,27 +357,37 @@ func (a *saOAuthClientAdapter) extractRedirectURIs(modelsMap map[string]model, n
318357

319358
for gk, models := range groupKindModelListMapper {
320359
if names := models.getNames(); names.Len() > 0 {
321-
if objMapper := groupKindModelToURI[gk](namespace, names); len(objMapper) > 0 {
360+
objMapper, errs := groupKindModelToURI[gk](namespace, names)
361+
if len(objMapper) > 0 {
322362
data = append(data, models.getRedirectURIs(objMapper)...)
323363
}
364+
if len(errs) > 0 {
365+
routeErrors = append(routeErrors, errs...)
366+
}
324367
}
325368
}
326369

327-
return data
370+
return data, routeErrors
328371
}
329372

330373
// redirectURIsFromRoutes is the namesToObjMapperFunc specific to Routes.
331374
// Returns a map of route name to redirect URIs that contain the default data as specified by the route's ingresses.
332-
func (a *saOAuthClientAdapter) redirectURIsFromRoutes(namespace string, osRouteNames sets.String) map[string]redirectURIList {
375+
// Errors returned are informative and non-fatal.
376+
func (a *saOAuthClientAdapter) redirectURIsFromRoutes(namespace string, osRouteNames sets.String) (map[string]redirectURIList, []error) {
333377
var routes []routeapi.Route
378+
routeErrors := []error{}
334379
routeInterface := a.routeClient.Routes(namespace)
335380
if osRouteNames.Len() > 1 {
336381
if r, err := routeInterface.List(metav1.ListOptions{}); err == nil {
337382
routes = r.Items
383+
} else {
384+
routeErrors = append(routeErrors, err)
338385
}
339386
} else {
340387
if r, err := routeInterface.Get(osRouteNames.List()[0], metav1.GetOptions{}); err == nil {
341388
routes = append(routes, *r)
389+
} else {
390+
routeErrors = append(routeErrors, err)
342391
}
343392
}
344393
routeMap := map[string]redirectURIList{}
@@ -347,7 +396,7 @@ func (a *saOAuthClientAdapter) redirectURIsFromRoutes(namespace string, osRouteN
347396
routeMap[route.Name] = redirectURIsFromRoute(&route)
348397
}
349398
}
350-
return routeMap
399+
return routeMap, routeErrors
351400
}
352401

353402
// redirectURIsFromRoute returns a list of redirect URIs that contain the default data as specified by the given route's ingresses.

0 commit comments

Comments
 (0)