Skip to content

Commit 659945d

Browse files
author
Matt Rogers
committed
Add API events for SA OAuth failures
Add user-viewable events when SA OAuth fails due to missing annotations or tokens, returning an internal error with a hint to view `oc get events` Signed-off-by: Matt Rogers <[email protected]>
1 parent f895b96 commit 659945d

File tree

6 files changed

+94
-23
lines changed

6 files changed

+94
-23
lines changed

pkg/cmd/server/origin/auth.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import (
6767
"github.com/openshift/origin/pkg/oauth/server/osinserver/registrystorage"
6868
oauthutil "github.com/openshift/origin/pkg/oauth/util"
6969
saoauth "github.com/openshift/origin/pkg/serviceaccounts/oauthclient"
70+
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
7071
)
7172

7273
const (
@@ -92,7 +93,14 @@ func (c *AuthConfig) WithOAuth(handler http.Handler) (http.Handler, error) {
9293
return nil, err
9394
}
9495
clientRegistry := clientregistry.NewRegistry(clientStorage)
95-
combinedOAuthClientGetter := saoauth.NewServiceAccountOAuthClientGetter(c.KubeClient.Core(), c.KubeClient.Core(), c.OpenShiftClient, clientRegistry, oauthapi.GrantHandlerType(c.Options.GrantConfig.ServiceAccountMethod))
96+
combinedOAuthClientGetter := saoauth.NewServiceAccountOAuthClientGetter(
97+
c.KubeClient.Core(),
98+
c.KubeClient.Core(),
99+
corev1.New(c.KubeExternalClient.Core().RESTClient()).Events(""),
100+
c.OpenShiftClient,
101+
clientRegistry,
102+
oauthapi.GrantHandlerType(c.Options.GrantConfig.ServiceAccountMethod),
103+
)
96104

97105
accessTokenStorage, err := accesstokenetcd.NewREST(c.RESTOptionsGetter, combinedOAuthClientGetter, c.EtcdBackends...)
98106
if err != nil {

pkg/cmd/server/origin/auth_config.go

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/pborman/uuid"
99

1010
"k8s.io/apiserver/pkg/storage"
11+
kclientsetexternal "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
1112
kclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
1213

1314
"github.com/openshift/origin/pkg/auth/server/session"
@@ -30,6 +31,8 @@ type AuthConfig struct {
3031
// KubeClient is kubeclient with enough permission for the auth API
3132
KubeClient kclientset.Interface
3233

34+
KubeExternalClient kclientsetexternal.Interface
35+
3336
// OpenShiftClient is osclient with enough permission for the auth API
3437
OpenShiftClient osclient.Interface
3538

@@ -52,6 +55,7 @@ type AuthConfig struct {
5255
func BuildAuthConfig(masterConfig *MasterConfig) (*AuthConfig, error) {
5356
options := masterConfig.Options
5457
osClient, kubeClient := masterConfig.OAuthServerClients()
58+
kubeExternalClient := masterConfig.KubeClientsetExternal()
5559

5660
var sessionAuth *session.Authenticator
5761
var sessionHandlerWrapper handlerWrapper
@@ -87,6 +91,7 @@ func BuildAuthConfig(masterConfig *MasterConfig) (*AuthConfig, error) {
8791
Options: *options.OAuthConfig,
8892

8993
KubeClient: kubeClient,
94+
KubeExternalClient: kubeExternalClient,
9095

9196
OpenShiftClient: osClient,
9297

pkg/cmd/server/origin/storage.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"k8s.io/apimachinery/pkg/runtime/schema"
1111
apirequest "k8s.io/apiserver/pkg/endpoints/request"
1212
"k8s.io/apiserver/pkg/registry/rest"
13+
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
1314
restclient "k8s.io/client-go/rest"
1415
"k8s.io/client-go/util/flowcontrol"
1516
kapi "k8s.io/kubernetes/pkg/api"
@@ -352,7 +353,15 @@ func (c OpenshiftAPIConfig) GetRestStorage() (map[schema.GroupVersion]map[string
352353
saAccountGrantMethod = oauthapi.GrantHandlerType(c.ServiceAccountMethod)
353354
}
354355

355-
combinedOAuthClientGetter := saoauth.NewServiceAccountOAuthClientGetter(c.KubeClientInternal.Core(), c.KubeClientInternal.Core(), c.DeprecatedOpenshiftClient, clientRegistry, saAccountGrantMethod)
356+
combinedOAuthClientGetter := saoauth.NewServiceAccountOAuthClientGetter(
357+
c.KubeClientInternal.Core(),
358+
c.KubeClientInternal.Core(),
359+
corev1.New(c.KubeClientExternal.Core().RESTClient()).Events(""),
360+
c.DeprecatedOpenshiftClient,
361+
clientRegistry,
362+
saAccountGrantMethod,
363+
)
364+
356365
authorizeTokenStorage, err := authorizetokenetcd.NewREST(c.GenericConfig.RESTOptionsGetter, combinedOAuthClientGetter)
357366
if err != nil {
358367
return nil, fmt.Errorf("error building REST storage: %v", err)

pkg/serviceaccounts/oauthclient/oauthclientregistry.go

+38-13
Original file line numberDiff line numberDiff line change
@@ -10,8 +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+
"k8s.io/apimachinery/pkg/util/sets"
1314
apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
1415
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"
1519
kapi "k8s.io/kubernetes/pkg/api"
1620
kcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
1721
"k8s.io/kubernetes/pkg/serviceaccount"
@@ -21,7 +25,6 @@ import (
2125
oauthapi "github.com/openshift/origin/pkg/oauth/apis/oauth"
2226
"github.com/openshift/origin/pkg/oauth/registry/oauthclient"
2327
routeapi "github.com/openshift/origin/pkg/route/apis/route"
24-
"k8s.io/apimachinery/pkg/util/sets"
2528
)
2629

2730
const (
@@ -58,9 +61,10 @@ var legacyRouteGroupKind = routeapi.LegacySchemeGroupVersion.WithKind(routeKind)
5861
// var ingressGroupKind = routeapi.SchemeGroupVersion.WithKind(IngressKind).GroupKind()
5962

6063
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
6468
// TODO add ingress support
6569
//ingressClient ??
6670

@@ -186,11 +190,14 @@ func (uri *redirectURI) merge(m *model) {
186190

187191
var _ oauthclient.Getter = &saOAuthClientAdapter{}
188192

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+
eventBroadcaster := record.NewBroadcaster()
195+
eventBroadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface: eventClient})
196+
recorder := eventBroadcaster.NewRecorder(kapi.Scheme, clientv1.EventSource{Component: "service-account-oauth-client-getter"})
197+
return &saOAuthClientAdapter{saClient: saClient, secretClient: secretClient, eventRecorder: recorder, routeClient: routeClient, delegate: delegate, grantMethod: grantMethod, decoder: kapi.Codecs.UniversalDecoder()}
191198
}
192199

193-
func (a *saOAuthClientAdapter) GetClient(ctx apirequest.Context, name string, options *metav1.GetOptions) (*oauthapi.OAuthClient, error) {
200+
func (a *saOAuthClientAdapter) GetClient(ctx apirequest.Context, name string, options *metav1.GetOptions) (saClient *oauthapi.OAuthClient, err error) {
194201
saNamespace, saName, err := apiserverserviceaccount.SplitUsername(name)
195202
if err != nil {
196203
return a.delegate.GetClient(ctx, name, options)
@@ -201,30 +208,44 @@ func (a *saOAuthClientAdapter) GetClient(ctx apirequest.Context, name string, op
201208
return nil, err
202209
}
203210

211+
failEvents := []string{}
212+
var failReason string
213+
// Create a warning event upon failure
214+
defer func() {
215+
if err != nil && len(failEvents) > 0 && len(failReason) > 0 {
216+
a.eventRecorder.Eventf(sa, kapi.EventTypeWarning, failReason, "%s", strings.Join(failEvents, ","))
217+
}
218+
}()
219+
204220
redirectURIs := []string{}
205-
if modelsMap := parseModelsMap(sa.Annotations, a.decoder); len(modelsMap) > 0 {
221+
if modelsMap := parseModelsMap(sa.Annotations, a.decoder, &failEvents); len(modelsMap) > 0 {
206222
if uris := a.extractRedirectURIs(modelsMap, saNamespace); len(uris) > 0 {
207223
redirectURIs = append(redirectURIs, uris.extractValidRedirectURIStrings()...)
208224
}
209225
}
210226
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>",
227+
err = fmt.Errorf("%v has no redirectURIs; set %v<some-value>=<redirect> or create a dynamic URI using %v<some-value>=<reference>",
213228
name, OAuthRedirectModelAnnotationURIPrefix, OAuthRedirectModelAnnotationReferencePrefix,
214229
)
230+
failReason = "NoSAOAuthRedirectURIs"
231+
failEvents = append(failEvents, err.Error())
232+
return nil, err
215233
}
216234

217235
tokens, err := a.getServiceAccountTokens(sa)
218236
if err != nil {
219237
return nil, err
220238
}
221239
if len(tokens) == 0 {
222-
return nil, fmt.Errorf("%v has no tokens", name)
240+
err = fmt.Errorf("%v has no tokens", name)
241+
failReason = "NoSAOAuthTokens"
242+
failEvents = append(failEvents, err.Error())
243+
return nil, err
223244
}
224245

225246
saWantsChallenges, _ := strconv.ParseBool(sa.Annotations[OAuthWantChallengesAnnotationPrefix])
226247

227-
saClient := &oauthapi.OAuthClient{
248+
saClient = &oauthapi.OAuthClient{
228249
ObjectMeta: metav1.ObjectMeta{Name: name},
229250
ScopeRestrictions: getScopeRestrictionsFor(saNamespace, saName),
230251
AdditionalSecrets: tokens,
@@ -243,7 +264,7 @@ func (a *saOAuthClientAdapter) GetClient(ctx apirequest.Context, name string, op
243264
// parseModelsMap builds a map of model name to model using a service account's annotations.
244265
// The model name is only used for building the map (it ties together the uri and reference annotations)
245266
// and serves no functional purpose other than making testing easier.
246-
func parseModelsMap(annotations map[string]string, decoder runtime.Decoder) map[string]model {
267+
func parseModelsMap(annotations map[string]string, decoder runtime.Decoder, fails *[]string) map[string]model {
247268
models := map[string]model{}
248269
for key, value := range annotations {
249270
prefix, name, ok := parseModelPrefixName(key)
@@ -255,11 +276,15 @@ func parseModelsMap(annotations map[string]string, decoder runtime.Decoder) map[
255276
case OAuthRedirectModelAnnotationURIPrefix:
256277
if u, err := url.Parse(value); err == nil {
257278
m.updateFromURI(u)
279+
} else {
280+
*fails = append(*fails, fmt.Sprintf("failed to parse SA annotation %q: %s", prefix, err.Error()))
258281
}
259282
case OAuthRedirectModelAnnotationReferencePrefix:
260283
r := &oauthapi.OAuthRedirectReference{}
261284
if err := runtime.DecodeInto(decoder, []byte(value), r); err == nil {
262285
m.updateFromReference(&r.Reference)
286+
} else {
287+
*fails = append(*fails, fmt.Sprintf("failed to decode SA annotation %q: %s", prefix, err.Error()))
263288
}
264289
}
265290
models[name] = m

pkg/serviceaccounts/oauthclient/oauthclientregistry_test.go

+31-7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"k8s.io/apimachinery/pkg/util/sets"
1313
apirequest "k8s.io/apiserver/pkg/endpoints/request"
1414
clientgotesting "k8s.io/client-go/testing"
15+
"k8s.io/client-go/tools/record"
1516
kapi "k8s.io/kubernetes/pkg/api"
1617
kapihelper "k8s.io/kubernetes/pkg/api/helper"
1718
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
@@ -41,6 +42,7 @@ func TestGetClient(t *testing.T) {
4142

4243
expectedDelegation bool
4344
expectedErr string
45+
expectedEventMsg string
4446
expectedClient *oauthapi.OAuthClient
4547
expectedKubeActions []clientgotesting.Action
4648
expectedOSActions []clientgotesting.Action
@@ -76,6 +78,7 @@ func TestGetClient(t *testing.T) {
7678
}),
7779
osClient: ostestclient.NewSimpleFake(),
7880
expectedErr: `system:serviceaccount:ns-01:default has no redirectURIs; set serviceaccounts.openshift.io/oauth-redirecturi.<some-value>`,
81+
expectedEventMsg: `Warning NoSAOAuthRedirectURIs system:serviceaccount:ns-01:default has no redirectURIs; set serviceaccounts.openshift.io/oauth-redirecturi.<some-value>=<redirect> or create a dynamic URI using serviceaccounts.openshift.io/oauth-redirectreference.<some-value>=<reference>`,
7982
expectedKubeActions: []clientgotesting.Action{clientgotesting.NewGetAction(serviceAccountsResource, "ns-01", "default")},
8083
expectedOSActions: []clientgotesting.Action{},
8184
},
@@ -90,8 +93,9 @@ func TestGetClient(t *testing.T) {
9093
Annotations: map[string]string{OAuthRedirectModelAnnotationURIPrefix + "one": "http://anywhere"},
9194
},
9295
}),
93-
osClient: ostestclient.NewSimpleFake(),
94-
expectedErr: `system:serviceaccount:ns-01:default has no tokens`,
96+
osClient: ostestclient.NewSimpleFake(),
97+
expectedErr: `system:serviceaccount:ns-01:default has no tokens`,
98+
expectedEventMsg: `Warning NoSAOAuthTokens system:serviceaccount:ns-01:default has no tokens`,
9599
expectedKubeActions: []clientgotesting.Action{
96100
clientgotesting.NewGetAction(serviceAccountsResource, "ns-01", "default"),
97101
clientgotesting.NewListAction(secretsResource, secretKind, "ns-01", metav1.ListOptions{}),
@@ -547,7 +551,17 @@ func TestGetClient(t *testing.T) {
547551

548552
for _, tc := range testCases {
549553
delegate := &fakeDelegate{}
550-
getter := NewServiceAccountOAuthClientGetter(tc.kubeClient.Core(), tc.kubeClient.Core(), tc.osClient, delegate, oauthapi.GrantHandlerPrompt)
554+
fakerecorder := record.NewFakeRecorder(100)
555+
getter := saOAuthClientAdapter{
556+
saClient: tc.kubeClient.Core(),
557+
secretClient: tc.kubeClient.Core(),
558+
eventRecorder: fakerecorder,
559+
routeClient: tc.osClient,
560+
delegate: delegate,
561+
grantMethod: oauthapi.GrantHandlerPrompt,
562+
decoder: kapi.Codecs.UniversalDecoder(),
563+
}
564+
551565
client, err := getter.GetClient(apirequest.NewContext(), tc.clientName, &metav1.GetOptions{})
552566
switch {
553567
case len(tc.expectedErr) == 0 && err == nil:
@@ -577,8 +591,14 @@ func TestGetClient(t *testing.T) {
577591
t.Errorf("%s: expected %#v, got %#v", tc.name, tc.expectedOSActions, tc.osClient.Actions())
578592
continue
579593
}
580-
}
581594

595+
if len(tc.expectedEventMsg) > 0 {
596+
ev := <-fakerecorder.Events
597+
if tc.expectedEventMsg != ev {
598+
t.Errorf("%s: expected event message %#v, got %#v", tc.name, tc.expectedEventMsg, ev)
599+
}
600+
}
601+
}
582602
}
583603

584604
type fakeDelegate struct {
@@ -816,8 +836,9 @@ func TestParseModelsMap(t *testing.T) {
816836
},
817837
},
818838
} {
819-
if !reflect.DeepEqual(test.expected, parseModelsMap(test.annotations, decoder)) {
820-
t.Errorf("%s: expected %#v, got %#v", test.name, test.expected, parseModelsMap(test.annotations, decoder))
839+
fails := []string{}
840+
if !reflect.DeepEqual(test.expected, parseModelsMap(test.annotations, decoder, &fails)) {
841+
t.Errorf("%s: expected %#v, got %#v", test.name, test.expected, parseModelsMap(test.annotations, decoder, &fails))
821842
}
822843
}
823844
}
@@ -1183,7 +1204,10 @@ func buildRouteClient(routes []*routeapi.Route) saOAuthClientAdapter {
11831204
for _, route := range routes {
11841205
objects = append(objects, route)
11851206
}
1186-
return saOAuthClientAdapter{routeClient: ostestclient.NewSimpleFake(objects...)}
1207+
return saOAuthClientAdapter{
1208+
routeClient: ostestclient.NewSimpleFake(objects...),
1209+
eventRecorder: record.NewFakeRecorder(100),
1210+
}
11871211
}
11881212

11891213
func buildRedirectObjectReferenceString(kind, name, group string) string {

vendor/github.com/RangelReale/osin/authorize.go

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)