Skip to content

Commit

Permalink
move the oauth server out of the main API server for structure
Browse files Browse the repository at this point in the history
  • Loading branch information
deads2k committed Aug 16, 2017
1 parent e2c9b75 commit 3bebaf4
Show file tree
Hide file tree
Showing 10 changed files with 222 additions and 84 deletions.
82 changes: 55 additions & 27 deletions pkg/cmd/server/origin/master.go
Expand Up @@ -38,6 +38,12 @@ import (
"github.com/openshift/origin/pkg/user/cache"
)

const (
openShiftOAuthAPIPrefix = "/oauth"
openShiftLoginPrefix = "/login"
openShiftOAuthCallbackPrefix = "/oauth2callback"
)

func (c *MasterConfig) newOpenshiftAPIConfig(kubeAPIServerConfig apiserver.Config) (*OpenshiftAPIConfig, error) {
// sccStorage must use the upstream RESTOptionsGetter to be in the correct location
// this probably creates a duplicate cache, but there are not very many SCCs, so live with it to avoid further linkage
Expand Down Expand Up @@ -199,6 +205,22 @@ func (c *MasterConfig) newAssetServerHandler() (http.Handler, error) {
return assetServer.GenericAPIServer.PrepareRun().GenericAPIServer.Handler.FullHandlerChain, nil
}

func (c *MasterConfig) newOAuthServerHandler() (http.Handler, error) {
if c.Options.OAuthConfig == nil {
return http.NotFoundHandler(), nil
}

config, err := NewOAuthServerConfigFromMasterConfig(c)
if err != nil {
return nil, err
}
oauthServer, err := config.Complete().New(apiserver.EmptyDelegate)
if err != nil {
return nil, err
}
return oauthServer.GenericAPIServer.PrepareRun().GenericAPIServer.Handler.FullHandlerChain, nil
}

func (c *MasterConfig) withAggregator(delegateAPIServer apiserver.DelegationTarget, kubeAPIServerConfig apiserver.Config, apiExtensionsInformers apiextensionsinformers.SharedInformerFactory) (*aggregatorapiserver.APIAggregator, error) {
aggregatorConfig, err := c.createAggregatorConfig(kubeAPIServerConfig)
if err != nil {
Expand All @@ -220,11 +242,10 @@ func (c *MasterConfig) Run(kubeAPIServerConfig *kubeapiserver.Config, controller
var apiExtensionsInformers apiextensionsinformers.SharedInformerFactory
var delegateAPIServer apiserver.DelegationTarget

assetHandler, err := c.newAssetServerHandler()
kubeAPIServerConfig.GenericConfig.BuildHandlerChainFunc, err = c.buildHandlerChain()
if err != nil {
return err
}
kubeAPIServerConfig.GenericConfig.BuildHandlerChainFunc = c.buildHandlerChain(assetHandler)

delegateAPIServer = apiserver.EmptyDelegate
delegateAPIServer, err = c.withTemplateServiceBroker(delegateAPIServer, *kubeAPIServerConfig.GenericConfig)
Expand Down Expand Up @@ -266,24 +287,29 @@ func (c *MasterConfig) Run(kubeAPIServerConfig *kubeapiserver.Config, controller

go aggregatedAPIServer.GenericAPIServer.PrepareRun().Run(stopCh)

// TODO find a better home for this output
if c.Options.OAuthConfig != nil {
glog.Infof("Starting OAuth2 API at %s", oauthutil.OpenShiftOAuthAPIPrefix)
}
if c.WebConsoleEnabled() && !c.WebConsoleStandalone() {
glog.Infof("Starting Web Console %s", c.Options.AssetConfig.PublicURL)
}

// Attempt to verify the server came up for 20 seconds (100 tries * 100ms, 100ms timeout per try)
return cmdutil.WaitForSuccessfulDial(true, aggregatedAPIServer.GenericAPIServer.SecureServingInfo.BindNetwork, aggregatedAPIServer.GenericAPIServer.SecureServingInfo.BindAddress, 100*time.Millisecond, 100*time.Millisecond, 100)
}

func (c *MasterConfig) buildHandlerChain(assetServerHandler http.Handler) func(apiHandler http.Handler, kc *apiserver.Config) http.Handler {
func (c *MasterConfig) buildHandlerChain() (func(apiHandler http.Handler, kc *apiserver.Config) http.Handler, error) {
assetServerHandler, err := c.newAssetServerHandler()
if err != nil {
return nil, err
}
oauthServerHandler, err := c.newOAuthServerHandler()
if err != nil {
return nil, err
}

return func(apiHandler http.Handler, genericConfig *apiserver.Config) http.Handler {
// these are after the kube handler
handler := c.versionSkewFilter(apiHandler, genericConfig.RequestContextMapper)
handler = namespacingFilter(handler, genericConfig.RequestContextMapper)

// these are all equivalent to the kube handler chain
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
handler = serverhandlers.AuthorizationFilter(handler, c.Authorizer, c.AuthorizationAttributeBuilder, genericConfig.RequestContextMapper)
handler = serverhandlers.ImpersonationFilter(handler, c.Authorizer, cache.NewGroupCache(c.UserInformers.User().InternalVersion().Groups()), genericConfig.RequestContextMapper)

// audit handler must comes before the impersonationFilter to read the original user
if c.Options.AuditConfig.Enabled {
var writer io.Writer
Expand All @@ -307,20 +333,6 @@ func (c *MasterConfig) buildHandlerChain(assetServerHandler http.Handler) func(a
handler = apifilters.WithAudit(handler, genericConfig.RequestContextMapper, c.AuditBackend, auditPolicyChecker, genericConfig.LongRunningFunc)
}
handler = serverhandlers.AuthenticationHandlerFilter(handler, c.Authenticator, genericConfig.RequestContextMapper)
handler = namespacingFilter(handler, genericConfig.RequestContextMapper)
handler = cacheControlFilter(handler, "no-store") // protected endpoints should not be cached

if c.Options.OAuthConfig != nil {
authConfig, err := BuildAuthConfig(c)
if err != nil {
glog.Fatalf("Failed to setup OAuth2: %v", err)
}
handler, err = authConfig.WithOAuth(handler)
if err != nil {
glog.Fatalf("Failed to setup OAuth2: %v", err)
}
}

handler = apiserverfilters.WithCORS(handler, c.Options.CORSAllowedOrigins, nil, nil, nil, "true")
handler = apiserverfilters.WithTimeoutForNonLongRunningRequests(handler, genericConfig.RequestContextMapper, genericConfig.LongRunningFunc)
// TODO: MaxRequestsInFlight should be subdivided by intent, type of behavior, and speed of
Expand All @@ -330,16 +342,21 @@ func (c *MasterConfig) buildHandlerChain(assetServerHandler http.Handler) func(a
handler = apifilters.WithRequestInfo(handler, apiserver.NewRequestInfoResolver(genericConfig), genericConfig.RequestContextMapper)
handler = apirequest.WithRequestContext(handler, genericConfig.RequestContextMapper)
handler = apiserverfilters.WithPanicRecovery(handler)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// these handlers are all before the normal kube chain
handler = cacheControlFilter(handler, "no-store") // protected endpoints should not be cached

if c.WebConsoleEnabled() {
handler = assetapiserver.WithAssetServerRedirect(handler, c.Options.AssetConfig.PublicURL)
}
// these handlers are actually separate API servers which have their own handler chains.
// our server embeds these
handler = c.withConsoleRedirection(handler, assetServerHandler, c.Options.AssetConfig)
handler = c.withOAuthRedirection(handler, oauthServerHandler)

return handler
}
}, nil
}

func (c *MasterConfig) withConsoleRedirection(handler, assetServerHandler http.Handler, assetConfig *configapi.AssetConfig) http.Handler {
Expand All @@ -361,9 +378,20 @@ func (c *MasterConfig) withConsoleRedirection(handler, assetServerHandler http.H
if publicURL.Path[lastIndex] == '/' {
prefix = publicURL.Path[0:lastIndex]
}

glog.Infof("Starting Web Console %s", assetConfig.PublicURL)
return WithPatternPrefixHandler(handler, assetServerHandler, prefix)
}

func (c *MasterConfig) withOAuthRedirection(handler, oauthServerHandler http.Handler) http.Handler {
if c.Options.OAuthConfig == nil {
return handler
}

glog.Infof("Starting OAuth2 API at %s", oauthutil.OpenShiftOAuthAPIPrefix)
return WithPatternPrefixHandler(handler, oauthServerHandler, openShiftOAuthAPIPrefix, openShiftLoginPrefix, openShiftOAuthCallbackPrefix)
}

// RouteAllocator returns a route allocation controller.
func (c *MasterConfig) RouteAllocator() *routeallocationcontroller.RouteAllocationController {
osclient, kclient := c.RouteAllocatorClients()
Expand Down
6 changes: 0 additions & 6 deletions pkg/cmd/server/origin/master_config.go
Expand Up @@ -1053,12 +1053,6 @@ func (c *MasterConfig) KubeClientsetExternal() kclientsetexternal.Interface {
return c.PrivilegedLoopbackKubernetesClientsetExternal
}

// OAuthServerClients returns the openshift and kubernetes OAuth server client objects
// The returned clients are privileged
func (c *MasterConfig) OAuthServerClients() (*osclient.Client, kclientsetinternal.Interface) {
return c.PrivilegedLoopbackOpenShiftClient, c.PrivilegedLoopbackKubernetesClientsetInternal
}

// ServiceAccountRoleBindingClient returns the client object used to bind roles to service accounts
// It must have the following capabilities:
// get, list, update, create policyBindings and clusterPolicyBindings in all namespaces
Expand Down
68 changes: 68 additions & 0 deletions pkg/cmd/server/origin/oauth_apiserver_adapter.go
@@ -0,0 +1,68 @@
package origin

import (
"net"
"strconv"

configapi "github.com/openshift/origin/pkg/cmd/server/api"
"github.com/openshift/origin/pkg/cmd/server/crypto"
oauthapiserver "github.com/openshift/origin/pkg/oauth/apiserver"
apiserveroptions "k8s.io/apiserver/pkg/server/options"
utilflag "k8s.io/apiserver/pkg/util/flag"
)

// TODO this is taking a very large config for a small piece of it. The information must be broken up at some point so that
// we can run this in a pod. This is an indication of leaky abstraction because it spent too much time in openshift start
func NewOAuthServerConfigFromMasterConfig(masterConfig *MasterConfig) (*oauthapiserver.OAuthServerConfig, error) {
options := masterConfig.Options
servingConfig := options.ServingInfo
oauthConfig := masterConfig.Options.OAuthConfig

oauthServerConfig, err := oauthapiserver.NewOAuthServerConfig(*oauthConfig, &masterConfig.PrivilegedLoopbackClientConfig)
if err != nil {
return nil, err
}

oauthServerConfig.GenericConfig.CorsAllowedOriginList = options.CORSAllowedOrigins

// TODO pull this out into a function
_, portString, err := net.SplitHostPort(servingConfig.BindAddress)
if err != nil {
return nil, err
}
port, err := strconv.Atoi(portString)
if err != nil {
return nil, err
}
secureServingOptions := apiserveroptions.SecureServingOptions{}
secureServingOptions.BindPort = port
secureServingOptions.ServerCert.CertKey.CertFile = servingConfig.ServerCert.CertFile
secureServingOptions.ServerCert.CertKey.KeyFile = servingConfig.ServerCert.KeyFile
for _, nc := range servingConfig.NamedCertificates {
sniCert := utilflag.NamedCertKey{
CertFile: nc.CertFile,
KeyFile: nc.KeyFile,
Names: nc.Names,
}
secureServingOptions.SNICertKeys = append(secureServingOptions.SNICertKeys, sniCert)
}
if err := secureServingOptions.ApplyTo(oauthServerConfig.GenericConfig); err != nil {
return nil, err
}
oauthServerConfig.GenericConfig.SecureServingInfo.BindAddress = servingConfig.BindAddress
oauthServerConfig.GenericConfig.SecureServingInfo.BindNetwork = servingConfig.BindNetwork
oauthServerConfig.GenericConfig.SecureServingInfo.MinTLSVersion = crypto.TLSVersionOrDie(servingConfig.MinTLSVersion)
oauthServerConfig.GenericConfig.SecureServingInfo.CipherSuites = crypto.CipherSuitesOrDie(servingConfig.CipherSuites)

oauthServerConfig.RESTOptionsGetter = masterConfig.RESTOptionsGetter
// TODO pass a privileged client config through during construction. It is NOT a loopback client.
oauthServerConfig.OpenShiftClient = masterConfig.PrivilegedLoopbackOpenShiftClient
oauthServerConfig.KubeClient = masterConfig.PrivilegedLoopbackKubernetesClientsetInternal

// Build the list of valid redirect_uri prefixes for a login using the openshift-web-console client to redirect to
if !options.DisabledFeatures.Has(configapi.FeatureWebConsole) {
oauthServerConfig.AssetPublicAddresses = []string{oauthConfig.AssetPublicURL}
}

return oauthServerConfig, nil
}
26 changes: 13 additions & 13 deletions pkg/cmd/server/origin/auth.go → pkg/oauth/apiserver/auth.go
@@ -1,4 +1,4 @@
package origin
package apiserver

import (
"crypto/tls"
Expand Down Expand Up @@ -80,7 +80,7 @@ const (

// WithOAuth decorates the given handler by serving the OAuth2 endpoints while
// passing through all other requests to the given handler.
func (c *AuthConfig) WithOAuth(handler http.Handler) (http.Handler, error) {
func (c *OAuthServerConfig) WithOAuth(handler http.Handler) (http.Handler, error) {
baseMux := http.NewServeMux()
mux := c.possiblyWrapMux(baseMux)

Expand Down Expand Up @@ -191,7 +191,7 @@ func (c *AuthConfig) WithOAuth(handler http.Handler) (http.Handler, error) {
return baseMux, nil
}

func (c *AuthConfig) possiblyWrapMux(mux cmdutil.Mux) cmdutil.Mux {
func (c *OAuthServerConfig) possiblyWrapMux(mux cmdutil.Mux) cmdutil.Mux {
// Register directly into the given mux
if c.HandlerWrapper == nil {
return mux
Expand All @@ -205,7 +205,7 @@ func (c *AuthConfig) possiblyWrapMux(mux cmdutil.Mux) cmdutil.Mux {
}
}

func (c *AuthConfig) getErrorHandler() (*errorpage.ErrorPage, error) {
func (c *OAuthServerConfig) getErrorHandler() (*errorpage.ErrorPage, error) {
errorTemplate := ""
if c.Options.Templates != nil {
errorTemplate = c.Options.Templates.Error
Expand All @@ -218,7 +218,7 @@ func (c *AuthConfig) getErrorHandler() (*errorpage.ErrorPage, error) {
}

// NewOpenShiftOAuthClientConfig provides config for OpenShift OAuth client
func (c *AuthConfig) NewOpenShiftOAuthClientConfig(client *oauthapi.OAuthClient) *osincli.ClientConfig {
func (c *OAuthServerConfig) NewOpenShiftOAuthClientConfig(client *oauthapi.OAuthClient) *osincli.ClientConfig {
config := &osincli.ClientConfig{
ClientId: client.Name,
ClientSecret: client.Secret,
Expand Down Expand Up @@ -321,12 +321,12 @@ func CreateOrUpdateDefaultOAuthClients(masterPublicAddr string, assetPublicAddre
}

// getCSRF returns the object responsible for generating and checking CSRF tokens
func (c *AuthConfig) getCSRF() csrf.CSRF {
func (c *OAuthServerConfig) getCSRF() csrf.CSRF {
secure := isHTTPS(c.Options.MasterPublicURL)
return csrf.NewCookieCSRF("csrf", "/", "", secure, true)
}

func (c *AuthConfig) getAuthorizeAuthenticationHandlers(mux cmdutil.Mux, errorHandler handlers.AuthenticationErrorHandler) (authenticator.Request, handlers.AuthenticationHandler, osinserver.AuthorizeHandler, error) {
func (c *OAuthServerConfig) getAuthorizeAuthenticationHandlers(mux cmdutil.Mux, errorHandler handlers.AuthenticationErrorHandler) (authenticator.Request, handlers.AuthenticationHandler, osinserver.AuthorizeHandler, error) {
authRequestHandler, err := c.getAuthenticationRequestHandler()
if err != nil {
return nil, nil, nil, err
Expand All @@ -341,7 +341,7 @@ func (c *AuthConfig) getAuthorizeAuthenticationHandlers(mux cmdutil.Mux, errorHa
}

// getGrantHandler returns the object that handles approving or rejecting grant requests
func (c *AuthConfig) getGrantHandler(mux cmdutil.Mux, auth authenticator.Request, clientregistry clientregistry.Getter, authregistry clientauthregistry.Registry) handlers.GrantHandler {
func (c *OAuthServerConfig) getGrantHandler(mux cmdutil.Mux, auth authenticator.Request, clientregistry clientregistry.Getter, authregistry clientauthregistry.Registry) handlers.GrantHandler {
// check that the global default strategy is something we honor
if !configapi.ValidGrantHandlerTypes.Has(string(c.Options.GrantConfig.Method)) {
glog.Fatalf("No grant handler found that matches %v. The OAuth server cannot start!", c.Options.GrantConfig.Method)
Expand All @@ -360,7 +360,7 @@ func (c *AuthConfig) getGrantHandler(mux cmdutil.Mux, auth authenticator.Request
}

// getAuthenticationFinalizer returns an authentication finalizer which is called just prior to writing a response to an authorization request
func (c *AuthConfig) getAuthenticationFinalizer() osinserver.AuthorizeHandler {
func (c *OAuthServerConfig) getAuthenticationFinalizer() osinserver.AuthorizeHandler {
if c.SessionAuth != nil {
// The session needs to know the authorize flow is done so it can invalidate the session
return osinserver.AuthorizeHandlerFunc(func(ar *osin.AuthorizeRequest, resp *osin.Response, w http.ResponseWriter) (bool, error) {
Expand All @@ -375,7 +375,7 @@ func (c *AuthConfig) getAuthenticationFinalizer() osinserver.AuthorizeHandler {
})
}

func (c *AuthConfig) getAuthenticationHandler(mux cmdutil.Mux, errorHandler handlers.AuthenticationErrorHandler) (handlers.AuthenticationHandler, error) {
func (c *OAuthServerConfig) getAuthenticationHandler(mux cmdutil.Mux, errorHandler handlers.AuthenticationErrorHandler) (handlers.AuthenticationHandler, error) {
// TODO: make this ordered once we can have more than one
challengers := map[string]handlers.AuthenticationChallenger{}

Expand Down Expand Up @@ -520,7 +520,7 @@ func (c *AuthConfig) getAuthenticationHandler(mux cmdutil.Mux, errorHandler hand
return authHandler, nil
}

func (c *AuthConfig) getOAuthProvider(identityProvider configapi.IdentityProvider) (external.Provider, error) {
func (c *OAuthServerConfig) getOAuthProvider(identityProvider configapi.IdentityProvider) (external.Provider, error) {
switch provider := identityProvider.Provider.(type) {
case (*configapi.GitHubIdentityProvider):
clientSecret, err := configapi.ResolveStringValue(provider.ClientSecret)
Expand Down Expand Up @@ -588,7 +588,7 @@ func (c *AuthConfig) getOAuthProvider(identityProvider configapi.IdentityProvide

}

func (c *AuthConfig) getPasswordAuthenticator(identityProvider configapi.IdentityProvider) (authenticator.Password, error) {
func (c *OAuthServerConfig) getPasswordAuthenticator(identityProvider configapi.IdentityProvider) (authenticator.Password, error) {
identityMapper, err := identitymapper.NewIdentityUserMapper(c.IdentityClient, c.UserClient, c.UserIdentityMappingClient, identitymapper.MappingMethodType(identityProvider.MappingMethod))
if err != nil {
return nil, err
Expand Down Expand Up @@ -667,7 +667,7 @@ func (c *AuthConfig) getPasswordAuthenticator(identityProvider configapi.Identit

}

func (c *AuthConfig) getAuthenticationRequestHandler() (authenticator.Request, error) {
func (c *OAuthServerConfig) getAuthenticationRequestHandler() (authenticator.Request, error) {
var authRequestHandlers []authenticator.Request

if c.SessionAuth != nil {
Expand Down
@@ -1,4 +1,4 @@
package origin
package apiserver

import (
"net/http"
Expand Down

0 comments on commit 3bebaf4

Please sign in to comment.