Skip to content

Commit 7e44f15

Browse files
committed
add proxy for the webconsole
1 parent a90f8af commit 7e44f15

File tree

6 files changed

+109
-376
lines changed

6 files changed

+109
-376
lines changed

pkg/cmd/server/origin/master.go

+8-16
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616
kubeapiserver "k8s.io/kubernetes/pkg/master"
1717
kcorestorage "k8s.io/kubernetes/pkg/registry/core/rest"
1818

19+
"io/ioutil"
20+
1921
assetapiserver "github.com/openshift/origin/pkg/assets/apiserver"
2022
configapi "github.com/openshift/origin/pkg/cmd/server/api"
2123
"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
@@ -154,22 +156,16 @@ func (c *MasterConfig) withKubeAPI(delegateAPIServer apiserver.DelegationTarget,
154156
return preparedKubeAPIServer.GenericAPIServer, nil
155157
}
156158

157-
func (c *MasterConfig) newAssetServerHandler(genericConfig *apiserver.Config) (http.Handler, error) {
158-
if !c.WebConsoleEnabled() || c.WebConsoleStandalone() {
159-
return http.NotFoundHandler(), nil
160-
}
161-
162-
config, err := assetapiserver.NewAssetServerConfig(*c.Options.AssetConfig, genericConfig.SecureServingInfo.Listener)
159+
func (c *MasterConfig) newWebConsoleProxy() (http.Handler, error) {
160+
caBundle, err := ioutil.ReadFile(c.Options.ControllerConfig.ServiceServingCert.Signer.CertFile)
163161
if err != nil {
164162
return nil, err
165163
}
166-
config.GenericConfig.AuditBackend = genericConfig.AuditBackend
167-
config.GenericConfig.AuditPolicyChecker = genericConfig.AuditPolicyChecker
168-
assetServer, err := config.Complete().New(apiserver.EmptyDelegate)
164+
proxyHandler, err := NewServiceProxyHandler("webconsole", "openshift-web-console", aggregatorapiserver.NewClusterIPServiceResolver(c.ClientGoKubeInformers.Core().V1().Services().Lister()), caBundle)
169165
if err != nil {
170166
return nil, err
171167
}
172-
return assetServer.GenericAPIServer.PrepareRun().GenericAPIServer.Handler.FullHandlerChain, nil
168+
return proxyHandler, nil
173169
}
174170

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

270266
func (c *MasterConfig) buildHandlerChain(genericConfig *apiserver.Config) (func(apiHandler http.Handler, kc *apiserver.Config) http.Handler, map[string]apiserver.PostStartHookFunc, error) {
271-
assetServerHandler, err := c.newAssetServerHandler(genericConfig)
267+
webconsoleProxyHandler, err := c.newWebConsoleProxy()
272268
if err != nil {
273269
return nil, nil, err
274270
}
@@ -294,7 +290,7 @@ func (c *MasterConfig) buildHandlerChain(genericConfig *apiserver.Config) (func(
294290
}
295291
// these handlers are actually separate API servers which have their own handler chains.
296292
// our server embeds these
297-
handler = c.withConsoleRedirection(handler, assetServerHandler, c.Options.AssetConfig)
293+
handler = c.withConsoleRedirection(handler, webconsoleProxyHandler, c.Options.AssetConfig)
298294
handler = c.withOAuthRedirection(handler, oauthServerHandler)
299295

300296
return handler
@@ -307,9 +303,6 @@ func (c *MasterConfig) withConsoleRedirection(handler, assetServerHandler http.H
307303
if assetConfig == nil {
308304
return handler
309305
}
310-
if !c.WebConsoleEnabled() || c.WebConsoleStandalone() {
311-
return handler
312-
}
313306

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

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

pkg/cmd/server/origin/master_config.go

-4
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,3 @@ func newProjectAuthorizationCache(subjectLocator authorizer.SubjectLocator, name
259259
func (c *MasterConfig) WebConsoleEnabled() bool {
260260
return c.Options.AssetConfig != nil && !c.Options.DisabledFeatures.Has(configapi.FeatureWebConsole)
261261
}
262-
263-
func (c *MasterConfig) WebConsoleStandalone() bool {
264-
return c.Options.AssetConfig.ServingInfo.BindAddress != c.Options.ServingInfo.BindAddress
265-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package origin
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"net/url"
8+
9+
"k8s.io/apimachinery/pkg/api/errors"
10+
"k8s.io/apimachinery/pkg/runtime"
11+
utilnet "k8s.io/apimachinery/pkg/util/net"
12+
"k8s.io/apimachinery/pkg/util/proxy"
13+
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
14+
restclient "k8s.io/client-go/rest"
15+
)
16+
17+
// A ServiceResolver knows how to get a URL given a service.
18+
type ServiceResolver interface {
19+
ResolveEndpoint(namespace, name string) (*url.URL, error)
20+
}
21+
22+
// proxyHandler provides a http.Handler which will proxy traffic to locations
23+
// specified by items implementing Redirector.
24+
type serviceProxyHandler struct {
25+
serviceName string
26+
serviceNamespace string
27+
28+
// Endpoints based routing to map from cluster IP to routable IP
29+
serviceResolver ServiceResolver
30+
31+
// proxyRoundTripper is the re-useable portion of the transport. It does not vary with any request.
32+
proxyRoundTripper http.RoundTripper
33+
34+
restConfig *restclient.Config
35+
}
36+
37+
// NewServiceProxyHandler is a simple proxy that doesn't handle upgrades, passes headers directly through, and doesn't assert any identity.
38+
func NewServiceProxyHandler(serviceName string, serviceNamespace string, serviceResolver ServiceResolver, caBundle []byte) (*serviceProxyHandler, error) {
39+
restConfig := &restclient.Config{
40+
TLSClientConfig: restclient.TLSClientConfig{
41+
ServerName: serviceName + "." + serviceNamespace + ".svc",
42+
CAData: caBundle,
43+
},
44+
}
45+
proxyRoundTripper, err := restclient.TransportFor(restConfig)
46+
if err != nil {
47+
return nil, err
48+
}
49+
50+
return &serviceProxyHandler{
51+
serviceName: serviceName,
52+
serviceNamespace: serviceNamespace,
53+
serviceResolver: serviceResolver,
54+
proxyRoundTripper: proxyRoundTripper,
55+
restConfig: restConfig,
56+
}, nil
57+
}
58+
59+
func (r *serviceProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
60+
// write a new location based on the existing request pointed at the target service
61+
location := &url.URL{}
62+
location.Scheme = "https"
63+
rloc, err := r.serviceResolver.ResolveEndpoint(r.serviceNamespace, r.serviceName)
64+
if errors.IsNotFound(err) {
65+
http.Error(w, fmt.Sprintf("missing service (%s)", err.Error()), http.StatusNotFound)
66+
}
67+
if err != nil {
68+
http.Error(w, fmt.Sprintf("missing route (%s)", err.Error()), http.StatusInternalServerError)
69+
return
70+
}
71+
location.Host = rloc.Host
72+
location.Path = req.URL.Path
73+
location.RawQuery = req.URL.Query().Encode()
74+
75+
// WithContext creates a shallow clone of the request with the new context.
76+
newReq := req.WithContext(context.Background())
77+
newReq.Header = utilnet.CloneHeader(req.Header)
78+
newReq.URL = location
79+
80+
handler := proxy.NewUpgradeAwareHandler(location, r.proxyRoundTripper, false, false, &responder{w: w})
81+
handler.ServeHTTP(w, newReq)
82+
}
83+
84+
// responder implements rest.Responder for assisting a connector in writing objects or errors.
85+
type responder struct {
86+
w http.ResponseWriter
87+
}
88+
89+
// TODO this should properly handle content type negotiation
90+
// if the caller asked for protobuf and you write JSON bad things happen.
91+
func (r *responder) Object(statusCode int, obj runtime.Object) {
92+
responsewriters.WriteRawJSON(statusCode, obj, r.w)
93+
}
94+
95+
func (r *responder) Error(_ http.ResponseWriter, _ *http.Request, err error) {
96+
http.Error(r.w, err.Error(), http.StatusInternalServerError)
97+
}

pkg/cmd/server/start/start_master.go

-24
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"k8s.io/apimachinery/pkg/util/sets"
2222
"k8s.io/apimachinery/pkg/util/wait"
2323
utilwait "k8s.io/apimachinery/pkg/util/wait"
24-
genericapiserver "k8s.io/apiserver/pkg/server"
2524
clientgoclientset "k8s.io/client-go/kubernetes"
2625
"k8s.io/client-go/tools/cache"
2726
aggregatorinstall "k8s.io/kube-aggregator/pkg/apis/apiregistration/install"
@@ -32,7 +31,6 @@ import (
3231
kubelettypes "k8s.io/kubernetes/pkg/kubelet/types"
3332
kutilerrors "k8s.io/kubernetes/staging/src/k8s.io/apimachinery/pkg/util/errors"
3433

35-
assetapiserver "github.com/openshift/origin/pkg/assets/apiserver"
3634
"github.com/openshift/origin/pkg/cmd/server/admin"
3735
configapi "github.com/openshift/origin/pkg/cmd/server/api"
3836
configapilatest "github.com/openshift/origin/pkg/cmd/server/api/latest"
@@ -41,7 +39,6 @@ import (
4139
"github.com/openshift/origin/pkg/cmd/server/crypto"
4240
"github.com/openshift/origin/pkg/cmd/server/etcd"
4341
"github.com/openshift/origin/pkg/cmd/server/etcd/etcdserver"
44-
kubernetesmaster "github.com/openshift/origin/pkg/cmd/server/kubernetes/master"
4542
"github.com/openshift/origin/pkg/cmd/server/origin"
4643
origincontrollers "github.com/openshift/origin/pkg/cmd/server/origin/controller"
4744
originrest "github.com/openshift/origin/pkg/cmd/server/origin/rest"
@@ -569,27 +566,6 @@ func StartAPI(oc *origin.MasterConfig, controllerPlug plug.Plug) error {
569566
return err
570567
}
571568

572-
// if the webconsole is configured to be standalone, go ahead and create and run it
573-
if oc.WebConsoleEnabled() && oc.WebConsoleStandalone() {
574-
config, err := assetapiserver.NewAssetServerConfig(*oc.Options.AssetConfig, nil)
575-
if err != nil {
576-
return err
577-
}
578-
backend, policy, err := kubernetesmaster.GetAuditConfig(oc.Options.AuditConfig)
579-
if err != nil {
580-
return err
581-
}
582-
config.GenericConfig.AuditBackend = backend
583-
config.GenericConfig.AuditPolicyChecker = policy
584-
assetServer, err := config.Complete().New(genericapiserver.EmptyDelegate)
585-
if err != nil {
586-
return err
587-
}
588-
if err := assetapiserver.RunAssetServer(assetServer, utilwait.NeverStop); err != nil {
589-
return err
590-
}
591-
}
592-
593569
return nil
594570
}
595571

test/integration/web_console_access_test.go

+4-67
Original file line numberDiff line numberDiff line change
@@ -80,39 +80,12 @@ func tryAccessURL(t *testing.T, url string, expectedStatus int, expectedRedirect
8080
return resp
8181
}
8282

83-
func TestAccessOriginWebConsole(t *testing.T) {
84-
masterOptions, err := testserver.DefaultMasterOptions()
85-
if err != nil {
86-
t.Fatalf("unexpected error: %v", err)
87-
}
88-
if _, err := testserver.StartConfiguredMaster(masterOptions); err != nil {
89-
t.Fatalf("unexpected error: %v", err)
90-
}
91-
defer testserver.CleanupMasterEtcd(t, masterOptions)
92-
93-
for endpoint, exp := range map[string]struct {
94-
statusCode int
95-
location string
96-
}{
97-
"": {http.StatusFound, masterOptions.AssetConfig.PublicURL},
98-
"healthz": {http.StatusOK, ""},
99-
"login?then=%2F": {http.StatusOK, ""},
100-
"oauth/token/request": {http.StatusFound, masterOptions.AssetConfig.MasterPublicURL + "/oauth/authorize"},
101-
"console": {http.StatusMovedPermanently, "/console/"},
102-
"console/": {http.StatusOK, ""},
103-
"console/java": {http.StatusOK, ""},
104-
} {
105-
url := masterOptions.AssetConfig.MasterPublicURL + "/" + endpoint
106-
tryAccessURL(t, url, exp.statusCode, exp.location, nil)
107-
}
108-
}
109-
11083
func TestAccessDisabledWebConsole(t *testing.T) {
11184
masterOptions, err := testserver.DefaultMasterOptions()
11285
if err != nil {
11386
t.Fatalf("unexpected error: %v", err)
11487
}
115-
masterOptions.DisabledFeatures.Add(configapi.FeatureWebConsole)
88+
masterOptions.DisabledFeatures.Add(configapi.FeatureWebConsole) // this isn't controlling anything but the root redirect now
11689
if _, err := testserver.StartConfiguredMaster(masterOptions); err != nil {
11790
t.Fatalf("unexpected error: %v", err)
11891
}
@@ -136,9 +109,9 @@ func TestAccessDisabledWebConsole(t *testing.T) {
136109
"healthz": {http.StatusOK, ""},
137110
"login?then=%2F": {http.StatusOK, ""},
138111
"oauth/token/request": {http.StatusFound, masterOptions.AssetConfig.MasterPublicURL + "/oauth/authorize"},
139-
"console": {http.StatusForbidden, ""},
140-
"console/": {http.StatusForbidden, ""},
141-
"console/java": {http.StatusForbidden, ""},
112+
"console": {http.StatusNotFound, ""}, // without the service, you get a 404
113+
"console/": {http.StatusNotFound, ""}, // without the service, you get a 404
114+
"console/java": {http.StatusNotFound, ""}, // without the service, you get a 404
142115
} {
143116
url := masterOptions.AssetConfig.MasterPublicURL + "/" + endpoint
144117
tryAccessURL(t, url, exp.statusCode, exp.location, nil)
@@ -237,39 +210,3 @@ func TestAccessOriginWebConsoleMultipleIdentityProviders(t *testing.T) {
237210
tryAccessURL(t, url, exp.statusCode, exp.location, nil)
238211
}
239212
}
240-
241-
func TestAccessStandaloneOriginWebConsole(t *testing.T) {
242-
masterOptions, err := testserver.DefaultMasterOptions()
243-
if err != nil {
244-
t.Fatalf("unexpected error: %v", err)
245-
}
246-
247-
addr, err := testserver.FindAvailableBindAddress(13000, 13999)
248-
if err != nil {
249-
t.Fatalf("unexpected error: %v", err)
250-
}
251-
252-
masterOptions.AssetConfig.ServingInfo.BindAddress = addr
253-
assetBaseURL := "https://" + addr
254-
masterOptions.AssetConfig.PublicURL = assetBaseURL + "/console/"
255-
masterOptions.OAuthConfig.AssetPublicURL = assetBaseURL + "/console/"
256-
257-
if _, err := testserver.StartConfiguredMaster(masterOptions); err != nil {
258-
t.Fatalf("unexpected error: %v", err)
259-
}
260-
defer testserver.CleanupMasterEtcd(t, masterOptions)
261-
262-
for endpoint, exp := range map[string]struct {
263-
statusCode int
264-
location string
265-
}{
266-
"": {http.StatusFound, "/console/"},
267-
"blarg": {http.StatusNotFound, ""},
268-
"console": {http.StatusMovedPermanently, "/console/"},
269-
"console/": {http.StatusOK, ""},
270-
"console/java": {http.StatusOK, ""},
271-
} {
272-
url := assetBaseURL + "/" + endpoint
273-
tryAccessURL(t, url, exp.statusCode, exp.location, nil)
274-
}
275-
}

0 commit comments

Comments
 (0)