Skip to content

Commit

Permalink
add proxy for the webconsole
Browse files Browse the repository at this point in the history
  • Loading branch information
deads2k committed Jan 10, 2018
1 parent a90f8af commit 7e44f15
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 376 deletions.
24 changes: 8 additions & 16 deletions pkg/cmd/server/origin/master.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
kubeapiserver "k8s.io/kubernetes/pkg/master"
kcorestorage "k8s.io/kubernetes/pkg/registry/core/rest"

"io/ioutil"

assetapiserver "github.com/openshift/origin/pkg/assets/apiserver"
configapi "github.com/openshift/origin/pkg/cmd/server/api"
"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
Expand Down Expand Up @@ -154,22 +156,16 @@ func (c *MasterConfig) withKubeAPI(delegateAPIServer apiserver.DelegationTarget,
return preparedKubeAPIServer.GenericAPIServer, nil
}

func (c *MasterConfig) newAssetServerHandler(genericConfig *apiserver.Config) (http.Handler, error) {
if !c.WebConsoleEnabled() || c.WebConsoleStandalone() {
return http.NotFoundHandler(), nil
}

config, err := assetapiserver.NewAssetServerConfig(*c.Options.AssetConfig, genericConfig.SecureServingInfo.Listener)
func (c *MasterConfig) newWebConsoleProxy() (http.Handler, error) {
caBundle, err := ioutil.ReadFile(c.Options.ControllerConfig.ServiceServingCert.Signer.CertFile)
if err != nil {
return nil, err
}
config.GenericConfig.AuditBackend = genericConfig.AuditBackend
config.GenericConfig.AuditPolicyChecker = genericConfig.AuditPolicyChecker
assetServer, err := config.Complete().New(apiserver.EmptyDelegate)
proxyHandler, err := NewServiceProxyHandler("webconsole", "openshift-web-console", aggregatorapiserver.NewClusterIPServiceResolver(c.ClientGoKubeInformers.Core().V1().Services().Lister()), caBundle)
if err != nil {
return nil, err
}
return assetServer.GenericAPIServer.PrepareRun().GenericAPIServer.Handler.FullHandlerChain, nil
return proxyHandler, nil
}

func (c *MasterConfig) newOAuthServerHandler(genericConfig *apiserver.Config) (http.Handler, map[string]apiserver.PostStartHookFunc, error) {
Expand Down Expand Up @@ -268,7 +264,7 @@ func (c *MasterConfig) Run(controllerPlug plug.Plug, stopCh <-chan struct{}) err
}

func (c *MasterConfig) buildHandlerChain(genericConfig *apiserver.Config) (func(apiHandler http.Handler, kc *apiserver.Config) http.Handler, map[string]apiserver.PostStartHookFunc, error) {
assetServerHandler, err := c.newAssetServerHandler(genericConfig)
webconsoleProxyHandler, err := c.newWebConsoleProxy()
if err != nil {
return nil, nil, err
}
Expand All @@ -294,7 +290,7 @@ func (c *MasterConfig) buildHandlerChain(genericConfig *apiserver.Config) (func(
}
// 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.withConsoleRedirection(handler, webconsoleProxyHandler, c.Options.AssetConfig)
handler = c.withOAuthRedirection(handler, oauthServerHandler)

return handler
Expand All @@ -307,9 +303,6 @@ func (c *MasterConfig) withConsoleRedirection(handler, assetServerHandler http.H
if assetConfig == nil {
return handler
}
if !c.WebConsoleEnabled() || c.WebConsoleStandalone() {
return handler
}

publicURL, err := url.Parse(assetConfig.PublicURL)
if err != nil {
Expand All @@ -323,7 +316,6 @@ func (c *MasterConfig) withConsoleRedirection(handler, assetServerHandler http.H
prefix = publicURL.Path[0:lastIndex]
}

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

Expand Down
4 changes: 0 additions & 4 deletions pkg/cmd/server/origin/master_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,3 @@ func newProjectAuthorizationCache(subjectLocator authorizer.SubjectLocator, name
func (c *MasterConfig) WebConsoleEnabled() bool {
return c.Options.AssetConfig != nil && !c.Options.DisabledFeatures.Has(configapi.FeatureWebConsole)
}

func (c *MasterConfig) WebConsoleStandalone() bool {
return c.Options.AssetConfig.ServingInfo.BindAddress != c.Options.ServingInfo.BindAddress
}
97 changes: 97 additions & 0 deletions pkg/cmd/server/origin/service_proxy_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package origin

import (
"context"
"fmt"
"net/http"
"net/url"

"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/proxy"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
restclient "k8s.io/client-go/rest"
)

// A ServiceResolver knows how to get a URL given a service.
type ServiceResolver interface {
ResolveEndpoint(namespace, name string) (*url.URL, error)
}

// proxyHandler provides a http.Handler which will proxy traffic to locations
// specified by items implementing Redirector.
type serviceProxyHandler struct {
serviceName string
serviceNamespace string

// Endpoints based routing to map from cluster IP to routable IP
serviceResolver ServiceResolver

// proxyRoundTripper is the re-useable portion of the transport. It does not vary with any request.
proxyRoundTripper http.RoundTripper

restConfig *restclient.Config
}

// NewServiceProxyHandler is a simple proxy that doesn't handle upgrades, passes headers directly through, and doesn't assert any identity.
func NewServiceProxyHandler(serviceName string, serviceNamespace string, serviceResolver ServiceResolver, caBundle []byte) (*serviceProxyHandler, error) {
restConfig := &restclient.Config{
TLSClientConfig: restclient.TLSClientConfig{
ServerName: serviceName + "." + serviceNamespace + ".svc",
CAData: caBundle,
},
}
proxyRoundTripper, err := restclient.TransportFor(restConfig)
if err != nil {
return nil, err
}

return &serviceProxyHandler{
serviceName: serviceName,
serviceNamespace: serviceNamespace,
serviceResolver: serviceResolver,
proxyRoundTripper: proxyRoundTripper,
restConfig: restConfig,
}, nil
}

func (r *serviceProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// write a new location based on the existing request pointed at the target service
location := &url.URL{}
location.Scheme = "https"
rloc, err := r.serviceResolver.ResolveEndpoint(r.serviceNamespace, r.serviceName)
if errors.IsNotFound(err) {
http.Error(w, fmt.Sprintf("missing service (%s)", err.Error()), http.StatusNotFound)
}
if err != nil {
http.Error(w, fmt.Sprintf("missing route (%s)", err.Error()), http.StatusInternalServerError)
return
}
location.Host = rloc.Host
location.Path = req.URL.Path
location.RawQuery = req.URL.Query().Encode()

// WithContext creates a shallow clone of the request with the new context.
newReq := req.WithContext(context.Background())
newReq.Header = utilnet.CloneHeader(req.Header)
newReq.URL = location

handler := proxy.NewUpgradeAwareHandler(location, r.proxyRoundTripper, false, false, &responder{w: w})
handler.ServeHTTP(w, newReq)
}

// responder implements rest.Responder for assisting a connector in writing objects or errors.
type responder struct {
w http.ResponseWriter
}

// TODO this should properly handle content type negotiation
// if the caller asked for protobuf and you write JSON bad things happen.
func (r *responder) Object(statusCode int, obj runtime.Object) {
responsewriters.WriteRawJSON(statusCode, obj, r.w)
}

func (r *responder) Error(_ http.ResponseWriter, _ *http.Request, err error) {
http.Error(r.w, err.Error(), http.StatusInternalServerError)
}
24 changes: 0 additions & 24 deletions pkg/cmd/server/start/start_master.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
utilwait "k8s.io/apimachinery/pkg/util/wait"
genericapiserver "k8s.io/apiserver/pkg/server"
clientgoclientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
aggregatorinstall "k8s.io/kube-aggregator/pkg/apis/apiregistration/install"
Expand All @@ -32,7 +31,6 @@ import (
kubelettypes "k8s.io/kubernetes/pkg/kubelet/types"
kutilerrors "k8s.io/kubernetes/staging/src/k8s.io/apimachinery/pkg/util/errors"

assetapiserver "github.com/openshift/origin/pkg/assets/apiserver"
"github.com/openshift/origin/pkg/cmd/server/admin"
configapi "github.com/openshift/origin/pkg/cmd/server/api"
configapilatest "github.com/openshift/origin/pkg/cmd/server/api/latest"
Expand All @@ -41,7 +39,6 @@ import (
"github.com/openshift/origin/pkg/cmd/server/crypto"
"github.com/openshift/origin/pkg/cmd/server/etcd"
"github.com/openshift/origin/pkg/cmd/server/etcd/etcdserver"
kubernetesmaster "github.com/openshift/origin/pkg/cmd/server/kubernetes/master"
"github.com/openshift/origin/pkg/cmd/server/origin"
origincontrollers "github.com/openshift/origin/pkg/cmd/server/origin/controller"
originrest "github.com/openshift/origin/pkg/cmd/server/origin/rest"
Expand Down Expand Up @@ -569,27 +566,6 @@ func StartAPI(oc *origin.MasterConfig, controllerPlug plug.Plug) error {
return err
}

// if the webconsole is configured to be standalone, go ahead and create and run it
if oc.WebConsoleEnabled() && oc.WebConsoleStandalone() {
config, err := assetapiserver.NewAssetServerConfig(*oc.Options.AssetConfig, nil)
if err != nil {
return err
}
backend, policy, err := kubernetesmaster.GetAuditConfig(oc.Options.AuditConfig)
if err != nil {
return err
}
config.GenericConfig.AuditBackend = backend
config.GenericConfig.AuditPolicyChecker = policy
assetServer, err := config.Complete().New(genericapiserver.EmptyDelegate)
if err != nil {
return err
}
if err := assetapiserver.RunAssetServer(assetServer, utilwait.NeverStop); err != nil {
return err
}
}

return nil
}

Expand Down
71 changes: 4 additions & 67 deletions test/integration/web_console_access_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,39 +80,12 @@ func tryAccessURL(t *testing.T, url string, expectedStatus int, expectedRedirect
return resp
}

func TestAccessOriginWebConsole(t *testing.T) {
masterOptions, err := testserver.DefaultMasterOptions()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, err := testserver.StartConfiguredMaster(masterOptions); err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer testserver.CleanupMasterEtcd(t, masterOptions)

for endpoint, exp := range map[string]struct {
statusCode int
location string
}{
"": {http.StatusFound, masterOptions.AssetConfig.PublicURL},
"healthz": {http.StatusOK, ""},
"login?then=%2F": {http.StatusOK, ""},
"oauth/token/request": {http.StatusFound, masterOptions.AssetConfig.MasterPublicURL + "/oauth/authorize"},
"console": {http.StatusMovedPermanently, "/console/"},
"console/": {http.StatusOK, ""},
"console/java": {http.StatusOK, ""},
} {
url := masterOptions.AssetConfig.MasterPublicURL + "/" + endpoint
tryAccessURL(t, url, exp.statusCode, exp.location, nil)
}
}

func TestAccessDisabledWebConsole(t *testing.T) {
masterOptions, err := testserver.DefaultMasterOptions()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
masterOptions.DisabledFeatures.Add(configapi.FeatureWebConsole)
masterOptions.DisabledFeatures.Add(configapi.FeatureWebConsole) // this isn't controlling anything but the root redirect now
if _, err := testserver.StartConfiguredMaster(masterOptions); err != nil {
t.Fatalf("unexpected error: %v", err)
}
Expand All @@ -136,9 +109,9 @@ func TestAccessDisabledWebConsole(t *testing.T) {
"healthz": {http.StatusOK, ""},
"login?then=%2F": {http.StatusOK, ""},
"oauth/token/request": {http.StatusFound, masterOptions.AssetConfig.MasterPublicURL + "/oauth/authorize"},
"console": {http.StatusForbidden, ""},
"console/": {http.StatusForbidden, ""},
"console/java": {http.StatusForbidden, ""},
"console": {http.StatusNotFound, ""}, // without the service, you get a 404
"console/": {http.StatusNotFound, ""}, // without the service, you get a 404
"console/java": {http.StatusNotFound, ""}, // without the service, you get a 404
} {
url := masterOptions.AssetConfig.MasterPublicURL + "/" + endpoint
tryAccessURL(t, url, exp.statusCode, exp.location, nil)
Expand Down Expand Up @@ -237,39 +210,3 @@ func TestAccessOriginWebConsoleMultipleIdentityProviders(t *testing.T) {
tryAccessURL(t, url, exp.statusCode, exp.location, nil)
}
}

func TestAccessStandaloneOriginWebConsole(t *testing.T) {
masterOptions, err := testserver.DefaultMasterOptions()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

addr, err := testserver.FindAvailableBindAddress(13000, 13999)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

masterOptions.AssetConfig.ServingInfo.BindAddress = addr
assetBaseURL := "https://" + addr
masterOptions.AssetConfig.PublicURL = assetBaseURL + "/console/"
masterOptions.OAuthConfig.AssetPublicURL = assetBaseURL + "/console/"

if _, err := testserver.StartConfiguredMaster(masterOptions); err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer testserver.CleanupMasterEtcd(t, masterOptions)

for endpoint, exp := range map[string]struct {
statusCode int
location string
}{
"": {http.StatusFound, "/console/"},
"blarg": {http.StatusNotFound, ""},
"console": {http.StatusMovedPermanently, "/console/"},
"console/": {http.StatusOK, ""},
"console/java": {http.StatusOK, ""},
} {
url := assetBaseURL + "/" + endpoint
tryAccessURL(t, url, exp.statusCode, exp.location, nil)
}
}
Loading

0 comments on commit 7e44f15

Please sign in to comment.