Skip to content

Commit b823cab

Browse files
author
James Bardin
committed
Configure https-redirect per service
- allow http and https services to coexist - add test to make sure our redirect behaviour works in the first place
1 parent 1290b13 commit b823cab

File tree

7 files changed

+94
-19
lines changed

7 files changed

+94
-19
lines changed

admin_test.go

+57-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net"
99
"net/http"
1010
"net/http/httptest"
11+
"net/url"
1112
"sync"
1213

1314
"github.com/litl/shuttle/client"
@@ -58,7 +59,6 @@ func (s *HTTPSuite) SetUpSuite(c *C) {
5859

5960
httpsRouter := NewHostRouter(httpsServer)
6061
httpsRouter.Scheme = "https"
61-
httpsRouter.SSLOnly = sslOnly
6262

6363
httpsReady := make(chan bool)
6464
go httpsRouter.Start(httpsReady)
@@ -658,3 +658,59 @@ func (s *HTTPSuite) TestHTTPSRouter(c *C) {
658658
checkHTTP("https://alt.vhost2.test:"+s.httpsPort+"/addr", "alt.vhost2.test", srv2.addr, 200, c)
659659
checkHTTP("https://star.vhost2.test:"+s.httpsPort+"/addr", "star.vhost2.test", srv2.addr, 200, c)
660660
}
661+
662+
// Verify that Settting HTTPSRedirect on a service works as expected for https
663+
// and for X-Forwarded-Proto:https.
664+
func (s *HTTPSuite) TestHTTPSRedirect(c *C) {
665+
srv1 := s.backendServers[0]
666+
667+
svcCfgOne := client.ServiceConfig{
668+
Name: "VHostTest1",
669+
Addr: "127.0.0.1:9000",
670+
HTTPSRedirect: true,
671+
VirtualHosts: []string{"vhost1.test", "alt.vhost1.test", "star.vhost1.test"},
672+
Backends: []client.BackendConfig{
673+
{Addr: srv1.addr},
674+
},
675+
}
676+
677+
err := Registry.AddService(svcCfgOne)
678+
if err != nil {
679+
c.Fatal(err)
680+
}
681+
682+
client := &http.Client{
683+
Transport: &http.Transport{
684+
Dial: localDial,
685+
},
686+
CheckRedirect: func(req *http.Request, via []*http.Request) error {
687+
return fmt.Errorf("redirected")
688+
},
689+
}
690+
691+
// this should redirect to https
692+
reqHTTP, _ := http.NewRequest("HEAD", "http://localhost:"+s.httpPort+"/addr", nil)
693+
reqHTTP.Host = "vhost1.test"
694+
695+
resp, err := client.Do(reqHTTP)
696+
if err != nil {
697+
if err, ok := err.(*url.Error); ok {
698+
if err.Err.Error() != "redirected" {
699+
c.Fatal(err)
700+
}
701+
} else {
702+
c.Fatal(err)
703+
}
704+
}
705+
c.Assert(resp.StatusCode, Equals, http.StatusMovedPermanently)
706+
707+
// this should be OK
708+
reqHTTP.Header = map[string][]string{
709+
"X-Forwarded-Proto": {"https"},
710+
}
711+
resp, err = client.Do(reqHTTP)
712+
if err != nil {
713+
c.Fatal(err)
714+
}
715+
c.Assert(resp.StatusCode, Equals, http.StatusOK)
716+
}

client/config.go

+10
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ type Config struct {
6969
// backend service, including name resolution.
7070
DialTimeout int `json:"connect_timeout"`
7171

72+
// HTTPSRedirect when set to true, redirects non-https request to https on
73+
// all services. The request may either have Scheme set to 'https', or
74+
// have an "X-Forwarded-Proto: https" header.
75+
HTTPSRedirect bool `json:"https-redirect"`
76+
7277
// Services is a slice of ServiceConfig for each service. A service
7378
// corresponds to one listening connection, and a number of backends to
7479
// proxy.
@@ -189,6 +194,11 @@ type ServiceConfig struct {
189194
// backend service, including name resolution.
190195
DialTimeout int `json:"connect_timeout"`
191196

197+
// HTTPSRedirect when set to true, redirects non-https request to https. The
198+
// request may either have Scheme set to 'https', or have an
199+
// "X-Forwarded-Proto: https" header.
200+
HTTPSRedirect bool `json:"https-redirect"`
201+
192202
// Virtualhosts is a set of virtual hostnames for which this service should
193203
// handle HTTP requests.
194204
VirtualHosts []string `json:"virtual_hosts,omitempty"`

http.go

-14
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@ type HostRouter struct {
2929
// the http frontend
3030
server *http.Server
3131

32-
// automatically redirect to https
33-
SSLOnly bool
34-
3532
// HTTP/HTTPS
3633
Scheme string
3734

@@ -53,15 +50,6 @@ func (r *HostRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
5350
req.Header.Set("X-Request-Id", reqId)
5451
w.Header().Add("X-Request-Id", reqId)
5552

56-
if r.SSLOnly {
57-
if req.TLS != nil || req.Header.Get("X-Forwarded-Proto") != "https" {
58-
//TODO: verify RequestURI
59-
redirLoc := "https://" + req.Host + req.RequestURI
60-
http.Redirect(w, req, redirLoc, http.StatusMovedPermanently)
61-
return
62-
}
63-
}
64-
6553
var err error
6654
host := req.Host
6755

@@ -137,7 +125,6 @@ func startHTTPServer(wg *sync.WaitGroup) {
137125
}
138126

139127
httpRouter = NewHostRouter(httpServer)
140-
httpRouter.SSLOnly = sslOnly
141128

142129
httpRouter.Start(nil)
143130
}
@@ -224,7 +211,6 @@ func startHTTPSServer(wg *sync.WaitGroup) {
224211

225212
httpRouter = NewHostRouter(httpsServer)
226213
httpRouter.Scheme = "https"
227-
httpRouter.SSLOnly = sslOnly
228214

229215
httpRouter.Start(nil)
230216
}

main.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ var (
2626
// Debug logging
2727
debug bool
2828

29-
// Redirect to SSL endpoint
30-
sslOnly bool
29+
// Redirect to HTTPS endpoint
30+
httpsRedirect bool
3131

3232
// version flags
3333
version bool
@@ -47,8 +47,8 @@ func init() {
4747
flag.BoolVar(&debug, "debug", false, "verbose logging")
4848
flag.BoolVar(&version, "v", false, "display version")
4949

50-
// FIXME: we may only want this for one HTTPRouter
51-
flag.BoolVar(&sslOnly, "sslOnly", false, "require SSL")
50+
flag.BoolVar(&httpsRedirect, "https-redirect", false, "redirect all http vhost requests to https")
51+
flag.BoolVar(&httpsRedirect, "sslOnly", false, "require https (deprecated)")
5252

5353
flag.Parse()
5454
}

registry.go

+9
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,11 @@ func (s *ServiceRegistry) UpdateConfig(cfg client.Config) error {
172172
s.cfg.DialTimeout = cfg.DialTimeout
173173
}
174174

175+
// apply the https rediect flag
176+
if httpsRedirect {
177+
s.cfg.HTTPSRedirect = true
178+
}
179+
175180
invalidPorts := []string{
176181
// FIXME: lookup bound addresses some other way. We may have multiple
177182
// http listeners, as well as all listening Services.
@@ -275,6 +280,7 @@ func (s *ServiceRegistry) AddService(svcCfg client.ServiceConfig) error {
275280
// Replacing a configuration will shutdown the existing service, and start a
276281
// new one, which will cause the listening socket to be temporarily
277282
// unavailable.
283+
// FIXME: this doesn't update service configuration options!
278284
func (s *ServiceRegistry) UpdateService(newCfg client.ServiceConfig) error {
279285
s.Lock()
280286
defer s.Unlock()
@@ -572,4 +578,7 @@ func (s *ServiceRegistry) setServiceDefaults(svc *client.ServiceConfig) {
572578
if svc.DialTimeout == 0 && s.cfg.DialTimeout != 0 {
573579
svc.DialTimeout = s.cfg.DialTimeout
574580
}
581+
if s.cfg.HTTPSRedirect {
582+
svc.HTTPSRedirect = true
583+
}
575584
}

service.go

+12
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type Service struct {
2424
sync.Mutex
2525
Name string
2626
Addr string
27+
HTTPSRedirect bool
2728
VirtualHosts []string
2829
Backends []*Backend
2930
Balance string
@@ -97,6 +98,7 @@ func NewService(cfg client.ServiceConfig) *Service {
9798
CheckInterval: cfg.CheckInterval,
9899
Fall: cfg.Fall,
99100
Rise: cfg.Rise,
101+
HTTPSRedirect: cfg.HTTPSRedirect,
100102
VirtualHosts: cfg.VirtualHosts,
101103
ClientTimeout: time.Duration(cfg.ClientTimeout) * time.Millisecond,
102104
ServerTimeout: time.Duration(cfg.ServerTimeout) * time.Millisecond,
@@ -228,6 +230,7 @@ func (s *Service) Config() client.ServiceConfig {
228230
Name: s.Name,
229231
Addr: s.Addr,
230232
VirtualHosts: s.VirtualHosts,
233+
HTTPSRedirect: s.HTTPSRedirect,
231234
Balance: s.Balance,
232235
CheckInterval: s.CheckInterval,
233236
Fall: s.Fall,
@@ -552,6 +555,15 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
552555
atomic.AddInt64(&s.HTTPActive, 1)
553556
defer atomic.AddInt64(&s.HTTPActive, -1)
554557

558+
if s.HTTPSRedirect {
559+
if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") != "https" {
560+
//TODO: verify RequestURI
561+
redirLoc := "https://" + r.Host + r.RequestURI
562+
http.Redirect(w, r, redirLoc, http.StatusMovedPermanently)
563+
return
564+
}
565+
}
566+
555567
s.httpProxy.ServeHTTP(w, r, s.NextAddrs())
556568
}
557569

shuttle-cli/main.go

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func init() {
4141
configFS.IntVar(&cfg.ClientTimeout, "client-timeout", 0, "innactivity timeout for client connections")
4242
configFS.IntVar(&cfg.ServerTimeout, "server-timeout", 0, "innactivity timeout for server connections")
4343
configFS.IntVar(&cfg.DialTimeout, "dial-timeout", 0, "timeout for dialing new connections connections")
44+
configFS.BoolVar(&cfg.HTTPSRedirect, "https-redirect", false, "rediect all http requests to https")
4445

4546
serviceFS.StringVar(&serviceCfg.Addr, "address", "", "service listening address")
4647
serviceFS.StringVar(&serviceCfg.Network, "network", "", "service network type")
@@ -51,6 +52,7 @@ func init() {
5152
serviceFS.IntVar(&serviceCfg.ClientTimeout, "client-timeout", 0, "innactivity timeout for client connections")
5253
serviceFS.IntVar(&serviceCfg.ServerTimeout, "server-timeout", 0, "innactivity timeout for server connections")
5354
serviceFS.IntVar(&serviceCfg.DialTimeout, "dial-timeout", 0, "timeout for dialing new connections connections")
55+
serviceFS.BoolVar(&serviceCfg.HTTPSRedirect, "https-redirect", false, "rediect all http requests to https")
5456
serviceFS.Var(&vhosts, "vhost", "virtual host name. may be set multiple times")
5557
serviceFS.Var(&errorPages, "error-page", "location for http error code formatted as 'http://example.com/|500,503'. may be set multiple times")
5658

0 commit comments

Comments
 (0)