Skip to content

Commit b5a13ce

Browse files
authored
vicadmin redirect/force TLS (vmware#7960)
1 parent 19251ab commit b5a13ce

File tree

2 files changed

+100
-4
lines changed

2 files changed

+100
-4
lines changed

cmd/vicadmin/server.go

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ import (
1919
"compress/gzip"
2020
"crypto/tls"
2121
"crypto/x509"
22+
"fmt"
2223
"html/template"
24+
"io"
2325
"net"
2426
"net/http"
2527
"net/url"
@@ -75,6 +77,83 @@ const (
7577
genericErrorMessage = "Internal Server Error; see /var/log/vic/vicadmin.log for details" // for http errors that shouldn't be displayed in the browser to the user
7678
)
7779

80+
// Conn is a wrapper struct around net.Conn that implements custom functionality (TLS Check)
81+
type Conn struct {
82+
net.Conn
83+
b byte
84+
err error
85+
UncertainTLS bool
86+
}
87+
88+
// Read checks for TLS in the connection and returns number of bytes read
89+
func (c *Conn) Read(b []byte) (int, error) {
90+
// one time check to determine if TLS is in the connection
91+
if c.UncertainTLS {
92+
if len(b) == 0 {
93+
return 0, fmt.Errorf("invalid length of byte array, cannot proceed with TLS check")
94+
}
95+
c.UncertainTLS = false
96+
b[0] = c.b
97+
// if there's more bytes to read
98+
if len(b) > 1 && c.err == nil {
99+
// recurse on next byte
100+
n, e := c.Conn.Read(b[1:])
101+
// close connection if error during reading
102+
if e != nil {
103+
c.Conn.Close()
104+
}
105+
// return total number of bytes read (+ current) and pass error e
106+
return n + 1, e
107+
}
108+
// only one byte read
109+
return 1, c.err
110+
}
111+
// using the default Conn read
112+
return c.Conn.Read(b)
113+
}
114+
115+
// TLSRedirectListener is a wrapper struct around net.Listener that implements custom functionality (TLS Check)
116+
type TLSRedirectListener struct {
117+
net.Listener
118+
addr string
119+
config *tls.Config
120+
}
121+
122+
// Accept overrides the default listener Accept and adds a TLS check
123+
func (l *TLSRedirectListener) Accept() (net.Conn, error) {
124+
// call default net.listener.Accept first
125+
c, err := l.Listener.Accept()
126+
if err != nil {
127+
return nil, err
128+
}
129+
// create slice of size 1 for the first byte
130+
b := make([]byte, 1)
131+
// regular Conn read, b will be updated with byte val
132+
_, err = c.Read(b)
133+
if err != nil {
134+
c.Close()
135+
if err != io.EOF {
136+
return nil, err
137+
}
138+
}
139+
// wrap net.Conn in custom Conn struct
140+
con := &Conn{
141+
Conn: c,
142+
b: b[0],
143+
err: err,
144+
UncertainTLS: true,
145+
}
146+
// First byte is the hex byte 0x16 = 22
147+
// which means that this is a TLS “handshake” record
148+
if b[0] == 22 {
149+
// HTTPS creates Conn from our custom con/TLS config
150+
return tls.Server(con, l.config), nil
151+
}
152+
153+
// regular HTTP connection, return con
154+
return con, nil
155+
}
156+
78157
func (s *server) listen() error {
79158
defer trace.End(trace.Begin(""))
80159

@@ -95,7 +174,6 @@ func (s *server) listen() error {
95174
}
96175
if err != nil {
97176
log.Errorf("Could not load certificate from config - running without TLS: %s", err)
98-
99177
s.l, err = net.Listen("tcp", s.addr)
100178
return err
101179
}
@@ -145,7 +223,7 @@ func (s *server) listen() error {
145223
return err
146224
}
147225

148-
s.l = tls.NewListener(innerListener, tlsconfig)
226+
s.l = &TLSRedirectListener{Listener: innerListener, config: tlsconfig}
149227
return nil
150228
}
151229

@@ -158,9 +236,22 @@ func (s *server) AuthenticatedHandle(link string, h http.Handler) {
158236
s.Authenticated(link, h.ServeHTTP)
159237
}
160238

239+
// HTTPSRedirectHandle Redirects HTTP to HTTPS
240+
func HTTPSRedirectHandle(h http.Handler) http.Handler {
241+
redirectToHTTPS := func(w http.ResponseWriter, r *http.Request) {
242+
// If TLS is nil, request is not HTTPS, so we must redirect
243+
if r.TLS == nil {
244+
http.Redirect(w, r, fmt.Sprintf("https://%s%s", r.Host, r.RequestURI), http.StatusMovedPermanently)
245+
return
246+
}
247+
h.ServeHTTP(w, r)
248+
}
249+
return http.HandlerFunc(redirectToHTTPS)
250+
}
251+
161252
func (s *server) Handle(link string, h http.Handler) {
162253
log.Debugf("%s --- %s", time.Now().String(), link)
163-
s.mux.Handle(link, gorillacontext.ClearHandler(h))
254+
s.mux.Handle(link, gorillacontext.ClearHandler(HTTPSRedirectHandle(h)))
164255
}
165256

166257
// Enforces authentication on route `link` and runs `handler` on successful auth
@@ -259,7 +350,6 @@ func (s *server) Authenticated(link string, handler func(http.ResponseWriter, *h
259350
s.logoutHandler(w, r)
260351
return
261352
}
262-
263353
// if the date & remote IP on the cookie were valid, then the user is authenticated
264354
log.Infof("User with a valid auth cookie at %s is authenticated.", connectingAddr[0])
265355
handler(w, r)

tests/test-cases/Group9-VIC-Admin/9-01-VICAdmin-ShowHTML.robot

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ Get Login Page
2424
${rc} ${output}= Run And Return Rc And Output curl -sk %{VIC-ADMIN}/authentication
2525
Should contain ${output} <title>VCH Admin</title>
2626

27+
Check that HTTP Request Redirects to HTTPS
28+
${rc} ${output}= Run And Return Rc And Output curl -L -sk -vvv http://%{VCH-IP}:2378
29+
Should Contain ${output} SSL connection using TLS
30+
Should Contain ${output} 301 Moved Permanently
31+
Should not contain ${output} Empty reply from server
32+
2733
While Logged Out Fail To Display HTML
2834
${rc} ${output}= Run And Return Rc And Output curl -sk %{VIC-ADMIN}
2935
Should not contain ${output} <title>VIC: %{VCH-NAME}</title>

0 commit comments

Comments
 (0)