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

Add events to SA as OAuth client flow #13621

Merged
merged 2 commits into from
Sep 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion pkg/oauth/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
restclient "k8s.io/client-go/rest"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"

Expand Down Expand Up @@ -121,8 +122,19 @@ func (c *OAuthAPIServerConfig) newV1RESTStorage() (map[string]rest.Storage, erro
if err != nil {
return nil, err
}
coreV1Client, err := corev1.NewForConfig(c.CoreAPIServerClientConfig)
if err != nil {
return nil, err
}

combinedOAuthClientGetter := saoauth.NewServiceAccountOAuthClientGetter(coreClient, coreClient, routeClient, oauthClient.OAuthClients(), saAccountGrantMethod)
combinedOAuthClientGetter := saoauth.NewServiceAccountOAuthClientGetter(
coreClient,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Call the sub methods on this to get the specific interfaces you need.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This did not seem possible with coreClient, are you suggesting a change in NewServiceAccountOAuthClientGetter() argument types as well?

coreClient,
coreV1Client.Events(""),
routeClient,
oauthClient.OAuthClients(),
saAccountGrantMethod,
)
authorizeTokenStorage, err := authorizetokenetcd.NewREST(c.GenericConfig.RESTOptionsGetter, combinedOAuthClientGetter)
if err != nil {
return nil, fmt.Errorf("error building REST storage: %v", err)
Expand Down
9 changes: 8 additions & 1 deletion pkg/oauth/apiserver/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,14 @@ func (c *OAuthServerConfig) WithOAuth(handler http.Handler) (http.Handler, error
// pass through all other requests
mux.Handle("/", handler)

combinedOAuthClientGetter := saoauth.NewServiceAccountOAuthClientGetter(c.KubeClient.Core(), c.KubeClient.Core(), c.RouteClient.Route(), c.OAuthClientClient, oauthapi.GrantHandlerType(c.Options.GrantConfig.ServiceAccountMethod))
combinedOAuthClientGetter := saoauth.NewServiceAccountOAuthClientGetter(
c.KubeClient.Core(),
c.KubeClient.Core(),
c.EventsClient,
c.RouteClient.Route(),
c.OAuthClientClient,
oauthapi.GrantHandlerType(c.Options.GrantConfig.ServiceAccountMethod),
)

errorPageHandler, err := c.getErrorHandler()
if err != nil {
Expand Down
9 changes: 9 additions & 0 deletions pkg/oauth/apiserver/oauth_apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
apirequest "k8s.io/apiserver/pkg/endpoints/request"
genericapiserver "k8s.io/apiserver/pkg/server"
genericfilters "k8s.io/apiserver/pkg/server/filters"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest"
kapi "k8s.io/kubernetes/pkg/api"
kclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
Expand All @@ -40,6 +41,9 @@ type OAuthServerConfig struct {
// KubeClient is kubeclient with enough permission for the auth API
KubeClient kclientset.Interface

// EventsClient is for creating user events
EventsClient corev1.EventInterface

// RouteClient provides a client for OpenShift routes API.
RouteClient routeclient.Interface

Expand Down Expand Up @@ -80,11 +84,16 @@ func NewOAuthServerConfig(oauthConfig configapi.OAuthConfig, userClientConfig *r
if err != nil {
return nil, err
}
eventsClient, err := corev1.NewForConfig(userClientConfig)
if err != nil {
return nil, err
}

ret := &OAuthServerConfig{
GenericConfig: genericConfig,
Options: oauthConfig,
SessionAuth: sessionAuth,
EventsClient: eventsClient.Events(""),
IdentityClient: userClient.Identities(),
UserClient: userClient.Users(),
UserIdentityMappingClient: userClient.UserIdentityMappings(),
Expand Down
103 changes: 76 additions & 27 deletions pkg/serviceaccounts/oauthclient/oauthclientregistry.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
clientv1 "k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/tools/record"
kapi "k8s.io/kubernetes/pkg/api"
kcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/serviceaccount"
Expand All @@ -20,7 +25,6 @@ import (
"github.com/openshift/origin/pkg/oauth/registry/oauthclient"
routeapi "github.com/openshift/origin/pkg/route/apis/route"
routeclient "github.com/openshift/origin/pkg/route/generated/internalclientset/typed/route/internalversion"
"k8s.io/apimachinery/pkg/util/sets"
)

const (
Expand All @@ -46,8 +50,8 @@ var modelPrefixes = []string{
// namesToObjMapperFunc is linked to a given GroupKind.
// Based on the namespace and names provided, it builds a map of resource name to redirect URIs.
// The redirect URIs represent the default values as specified by the resource.
// These values can be overridden by user specified data.
type namesToObjMapperFunc func(namespace string, names sets.String) map[string]redirectURIList
// These values can be overridden by user specified data. Errors returned are informative and non-fatal.
type namesToObjMapperFunc func(namespace string, names sets.String) (map[string]redirectURIList, []error)

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

type saOAuthClientAdapter struct {
saClient kcoreclient.ServiceAccountsGetter
secretClient kcoreclient.SecretsGetter
routeClient routeclient.RoutesGetter
saClient kcoreclient.ServiceAccountsGetter
secretClient kcoreclient.SecretsGetter
eventRecorder record.EventRecorder
routeClient routeclient.RoutesGetter
// TODO add ingress support
//ingressClient ??

Expand Down Expand Up @@ -188,22 +193,27 @@ var _ oauthclient.Getter = &saOAuthClientAdapter{}
func NewServiceAccountOAuthClientGetter(
saClient kcoreclient.ServiceAccountsGetter,
secretClient kcoreclient.SecretsGetter,
eventClient corev1.EventInterface,
routeClient routeclient.RoutesGetter,
delegate oauthclient.Getter,
grantMethod oauthapi.GrantHandlerType,
) oauthclient.Getter {

eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface: eventClient})
recorder := eventBroadcaster.NewRecorder(kapi.Scheme, clientv1.EventSource{Component: "service-account-oauth-client-getter"})
return &saOAuthClientAdapter{
saClient: saClient,
secretClient: secretClient,
routeClient: routeClient,
delegate: delegate,
grantMethod: grantMethod,
decoder: kapi.Codecs.UniversalDecoder(),
saClient: saClient,
secretClient: secretClient,
eventRecorder: recorder,
routeClient: routeClient,
delegate: delegate,
grantMethod: grantMethod,
decoder: kapi.Codecs.UniversalDecoder(),
}
}

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

var saErrors []error
var failReason string
// Create a warning event combining the collected annotation errors upon failure.
defer func() {
if err != nil && len(saErrors) > 0 && len(failReason) > 0 {
a.eventRecorder.Event(sa, kapi.EventTypeWarning, failReason, utilerrors.NewAggregate(saErrors).Error())
}
}()

redirectURIs := []string{}
if modelsMap := parseModelsMap(sa.Annotations, a.decoder); len(modelsMap) > 0 {
if uris := a.extractRedirectURIs(modelsMap, saNamespace); len(uris) > 0 {
modelsMap, errs := parseModelsMap(sa.Annotations, a.decoder)
if len(errs) > 0 {
saErrors = append(saErrors, errs...)
}

if len(modelsMap) > 0 {
uris, extractErrors := a.extractRedirectURIs(modelsMap, saNamespace)
if len(uris) > 0 {
redirectURIs = append(redirectURIs, uris.extractValidRedirectURIStrings()...)
}
if len(extractErrors) > 0 {
saErrors = append(saErrors, extractErrors...)
}
}
if len(redirectURIs) == 0 {
return nil, fmt.Errorf(
"%v has no redirectURIs; set %v<some-value>=<redirect> or create a dynamic URI using %v<some-value>=<reference>",
err = fmt.Errorf("%v has no redirectURIs; set %v<some-value>=<redirect> or create a dynamic URI using %v<some-value>=<reference>",
name, OAuthRedirectModelAnnotationURIPrefix, OAuthRedirectModelAnnotationReferencePrefix,
)
failReason = "NoSAOAuthRedirectURIs"
saErrors = append(saErrors, err)
return nil, err
}

tokens, err := a.getServiceAccountTokens(sa)
if err != nil {
return nil, err
}
if len(tokens) == 0 {
return nil, fmt.Errorf("%v has no tokens", name)
err = fmt.Errorf("%v has no tokens", name)
failReason = "NoSAOAuthTokens"
saErrors = append(saErrors, err)
return nil, err
}

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

// parseModelsMap builds a map of model name to model using a service account's annotations.
// The model name is only used for building the map (it ties together the uri and reference annotations)
// and serves no functional purpose other than making testing easier.
func parseModelsMap(annotations map[string]string, decoder runtime.Decoder) map[string]model {
// and serves no functional purpose other than making testing easier. Errors returned are informative and non-fatal.
func parseModelsMap(annotations map[string]string, decoder runtime.Decoder) (map[string]model, []error) {
models := map[string]model{}
parseErrors := []error{}
for key, value := range annotations {
prefix, name, ok := parseModelPrefixName(key)
if !ok {
Expand All @@ -268,16 +302,20 @@ func parseModelsMap(annotations map[string]string, decoder runtime.Decoder) map[
case OAuthRedirectModelAnnotationURIPrefix:
if u, err := url.Parse(value); err == nil {
m.updateFromURI(u)
} else {
parseErrors = append(parseErrors, err)
}
case OAuthRedirectModelAnnotationReferencePrefix:
r := &oauthapi.OAuthRedirectReference{}
if err := runtime.DecodeInto(decoder, []byte(value), r); err == nil {
m.updateFromReference(&r.Reference)
} else {
parseErrors = append(parseErrors, err)
}
}
models[name] = m
}
return models
return models, parseErrors
}

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

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

for gk, models := range groupKindModelListMapper {
if names := models.getNames(); names.Len() > 0 {
if objMapper := groupKindModelToURI[gk](namespace, names); len(objMapper) > 0 {
objMapper, errs := groupKindModelToURI[gk](namespace, names)
if len(objMapper) > 0 {
data = append(data, models.getRedirectURIs(objMapper)...)
}
if len(errs) > 0 {
routeErrors = append(routeErrors, errs...)
}
}
}

return data
return data, routeErrors
}

// redirectURIsFromRoutes is the namesToObjMapperFunc specific to Routes.
// Returns a map of route name to redirect URIs that contain the default data as specified by the route's ingresses.
func (a *saOAuthClientAdapter) redirectURIsFromRoutes(namespace string, osRouteNames sets.String) map[string]redirectURIList {
// Errors returned are informative and non-fatal.
func (a *saOAuthClientAdapter) redirectURIsFromRoutes(namespace string, osRouteNames sets.String) (map[string]redirectURIList, []error) {
var routes []routeapi.Route
routeErrors := []error{}
routeInterface := a.routeClient.Routes(namespace)
if osRouteNames.Len() > 1 {
if r, err := routeInterface.List(metav1.ListOptions{}); err == nil {
routes = r.Items
} else {
routeErrors = append(routeErrors, err)
}
} else {
if r, err := routeInterface.Get(osRouteNames.List()[0], metav1.GetOptions{}); err == nil {
routes = append(routes, *r)
} else {
routeErrors = append(routeErrors, err)
}
}
routeMap := map[string]redirectURIList{}
Expand All @@ -347,7 +396,7 @@ func (a *saOAuthClientAdapter) redirectURIsFromRoutes(namespace string, osRouteN
routeMap[route.Name] = redirectURIsFromRoute(&route)
}
}
return routeMap
return routeMap, routeErrors
}

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