diff --git a/vendor/github.com/rancher/websocket-proxy/backend/backend.go b/vendor/github.com/rancher/websocket-proxy/backend/backend.go index 531ae373..9fabd61b 100644 --- a/vendor/github.com/rancher/websocket-proxy/backend/backend.go +++ b/vendor/github.com/rancher/websocket-proxy/backend/backend.go @@ -63,7 +63,9 @@ func connectToProxyWS(ws *websocket.Conn, handlers map[string]Handler) error { _, msg, err := ws.ReadMessage() if err != nil { log.WithFields(log.Fields{"error": err}).Error("Received error reading from socket. Exiting.") - close(responseChannel) + for _, msgChan := range responders { + close(msgChan) + } return err } diff --git a/vendor/github.com/rancher/websocket-proxy/proxy/apiinterceptor/filters/auth/rancher_token_validate_filter.go b/vendor/github.com/rancher/websocket-proxy/proxy/apiinterceptor/filters/auth/rancher_token_validate_filter.go index 483114b7..2f5d1843 100644 --- a/vendor/github.com/rancher/websocket-proxy/proxy/apiinterceptor/filters/auth/rancher_token_validate_filter.go +++ b/vendor/github.com/rancher/websocket-proxy/proxy/apiinterceptor/filters/auth/rancher_token_validate_filter.go @@ -61,86 +61,94 @@ func (f *TokenValidationFilter) ProcessFilter(filter model.FilterData, input mod log.Debugf("Request => %v", input) - var cookie []string - if input.Headers["Cookie"] == nil { + var cookie, authHeader []string + if input.Headers["Cookie"] == nil && input.Headers["Authorization"] == nil { output.Status = http.StatusOK - log.Debug("No Cookie found in request") + log.Debug("No Cookie or Auth headers found in request") return output, nil } + usingTokenCookie := false + if len(input.Headers["Cookie"]) >= 1 { cookie = input.Headers["Cookie"] + usingTokenCookie = true + } else if len(input.Headers["Authorization"]) >= 1 { + authHeader = input.Headers["Authorization"] } else { output.Status = http.StatusOK - log.Debug("No Cookie found in request") + log.Debug("No Cookie or Auth headers found in request") return output, nil } - var cookieString string - if len(cookie) >= 1 { - for i := range cookie { - if strings.Contains(cookie[i], "token") { - cookieString = cookie[i] + + var cookieString, tokenValue string + if usingTokenCookie { + if len(cookie) >= 1 { + for i := range cookie { + if strings.Contains(cookie[i], "token") { + cookieString = cookie[i] + } } + } else { + output.Status = http.StatusOK + log.Debug("No token found in cookie") + return output, nil } - } else { - output.Status = http.StatusOK - log.Debug("No token found in cookie") - return output, nil - } - tokens := strings.Split(cookieString, ";") - tokenValue := "" - if len(tokens) >= 1 { - for i := range tokens { - if strings.Contains(tokens[i], "token") { - if len(strings.Split(tokens[i], "=")) > 1 { - tokenValue = strings.Split(tokens[i], "=")[1] + tokens := strings.Split(cookieString, ";") + + if len(tokens) >= 1 { + for i := range tokens { + if strings.Contains(tokens[i], "token") { + if len(strings.Split(tokens[i], "=")) > 1 { + tokenValue = strings.Split(tokens[i], "=")[1] + } } - } + } + } else { + output.Status = http.StatusOK + log.Debug("No token found in cookie") + return output, nil + } + if tokenValue == "" { + output.Status = http.StatusOK + log.Debug("No token found in cookie") + return output, nil } - } else { - output.Status = http.StatusOK - log.Debug("No token found in cookie") - return output, nil - } - if tokenValue == "" { - output.Status = http.StatusOK - log.Debug("No token found in cookie") - return output, nil } //check if the token value is empty or not - if tokenValue != "" { + if tokenValue != "" || len(authHeader) >= 1 { log.Debugf("token:" + tokenValue) log.Debugf("envid:" + envid) projectID, accountID := "", "" var err error if envid != "" { - projectID, accountID, err = getAccountAndProject(f.rancherURL, envid, tokenValue) + projectID, accountID, err = getAccountAndProject(f.rancherURL, envid, tokenValue, authHeader) if err != nil { output.Status = http.StatusNotFound return output, fmt.Errorf("Error getting the accountid and projectid: %v", err) } if accountID == "Unauthorized" { output.Status = http.StatusUnauthorized - return output, fmt.Errorf("Token is expired or unauthorized") + return output, fmt.Errorf("Token or Auth keys expired or unauthorized") } if accountID == "" { output.Status = http.StatusForbidden - return output, fmt.Errorf("Token is forbidden to access the projectid") + return output, fmt.Errorf("Token or Auth keys forbidden to access the projectid") } } else { - accountID, err = getAccountID(f.rancherURL, tokenValue) + accountID, err = getAccountID(f.rancherURL, tokenValue, authHeader) if err != nil { output.Status = http.StatusNotFound return output, fmt.Errorf("Error getting the accountid : %v", err) } if accountID == "Unauthorized" { output.Status = http.StatusUnauthorized - return output, fmt.Errorf("Token is expired or unauthorized") + return output, fmt.Errorf("Token or Auth keys expired or unauthorized") } } @@ -163,11 +171,12 @@ func (f *TokenValidationFilter) ProcessFilter(filter model.FilterData, input mod log.Debugf("Response <= %v", output) } + return output, nil } //get the projectID and accountID from rancher API -func getAccountAndProject(host string, envid string, token string) (string, string, error) { +func getAccountAndProject(host string, envid string, token string, authHeaders []string) (string, string, error) { client := &http.Client{} requestURL := host + "/v2-beta/projects/" + envid + "/accounts" @@ -176,8 +185,13 @@ func getAccountAndProject(host string, envid string, token string) (string, stri if err != nil { return "", "", fmt.Errorf("Cannot connect to the rancher server. Please check the rancher server URL") } - cookie := http.Cookie{Name: "token", Value: token} - req.AddCookie(&cookie) + if token != "" { + cookie := http.Cookie{Name: "token", Value: token} + req.AddCookie(&cookie) + } else { + req.Header["Authorization"] = authHeaders + } + resp, err := client.Do(req) if err != nil { return "", "", fmt.Errorf("Cannot connect to the rancher server. Please check the rancher server URL") @@ -211,7 +225,7 @@ func getAccountAndProject(host string, envid string, token string) (string, stri } //get the accountID from rancher API -func getAccountID(host string, token string) (string, error) { +func getAccountID(host string, token string, authHeaders []string) (string, error) { client := &http.Client{} requestURL := host + "/v2-beta/accounts" @@ -219,8 +233,14 @@ func getAccountID(host string, token string) (string, error) { if err != nil { return "", fmt.Errorf("Cannot get the account api [%v]", err) } - cookie := http.Cookie{Name: "token", Value: token} - req.AddCookie(&cookie) + + if token != "" { + cookie := http.Cookie{Name: "token", Value: token} + req.AddCookie(&cookie) + } else { + req.Header["Authorization"] = authHeaders + } + resp, err := client.Do(req) if err != nil { return "", fmt.Errorf("Cannot setup HTTP client [%v]", err) diff --git a/vendor/github.com/rancher/websocket-proxy/proxy/backend_http_writer.go b/vendor/github.com/rancher/websocket-proxy/proxy/backend_http_writer.go index c2a9300f..caa39de2 100644 --- a/vendor/github.com/rancher/websocket-proxy/proxy/backend_http_writer.go +++ b/vendor/github.com/rancher/websocket-proxy/proxy/backend_http_writer.go @@ -35,11 +35,23 @@ func (b *BackendHTTPWriter) Close() error { func (b *BackendHTTPWriter) WriteRequest(req *http.Request, hijack bool, address, scheme string) error { vars := mux.Vars(req) + headers := http.Header{} + for k, v := range req.Header { + headers[k] = v + } + url := *req.URL - url.Host = address - if path, ok := vars["path"]; ok { - url.Path = path + + if req.TLS == nil { + url.Scheme = "http" + } else { + url.Scheme = "https" } + url.Host = req.Host + headers.Set("X-API-request-url", url.String()) + + url.Host = address + url.Path = vars["path"] if !strings.HasPrefix(url.Path, "/") { url.Path = "/" + url.Path } @@ -55,7 +67,7 @@ func (b *BackendHTTPWriter) WriteRequest(req *http.Request, hijack bool, address Host: req.Host, Method: req.Method, URL: url.String(), - Headers: map[string][]string(req.Header), + Headers: headers, }) } diff --git a/vendor/github.com/rancher/websocket-proxy/proxy/frontend_handler.go b/vendor/github.com/rancher/websocket-proxy/proxy/frontend_handler.go index af267479..8355aecf 100644 --- a/vendor/github.com/rancher/websocket-proxy/proxy/frontend_handler.go +++ b/vendor/github.com/rancher/websocket-proxy/proxy/frontend_handler.go @@ -118,7 +118,7 @@ func (h *FrontendHandler) auth(req *http.Request) (*jwt.Token, string, error) { token, tokenParam, err := parseToken(req, h.parsedPublicKey) if err != nil { if tokenParam == "" { - return nil, "", noTokenError{} + return nil, "", noAuthError{err: err.Error()} } return nil, "", fmt.Errorf("Error parsing token: %v. Token parameter: %v", err, tokenParam) } @@ -158,20 +158,26 @@ func parseToken(req *http.Request, parsedPublicKey interface{}) (*jwt.Token, str return nil, "", fmt.Errorf("No JWT provided") } + parts := strings.Split(tokenString, ".") + if len(parts) != 3 { + return nil, "", fmt.Errorf("No JWT provided") + } + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { return parsedPublicKey, nil }) return token, tokenString, err } -type noTokenError struct { +type noAuthError struct { + err string } -func (e noTokenError) Error() string { - return "Request did not have a token parameter." +func (e noAuthError) Error() string { + return e.err } -func IsNoTokenError(err error) bool { - _, ok := err.(noTokenError) +func IsNoAuthError(err error) bool { + _, ok := err.(noAuthError) return ok } diff --git a/vendor/github.com/rancher/websocket-proxy/proxy/frontend_http_handler.go b/vendor/github.com/rancher/websocket-proxy/proxy/frontend_http_handler.go index 0bc06a5f..8950fbac 100644 --- a/vendor/github.com/rancher/websocket-proxy/proxy/frontend_http_handler.go +++ b/vendor/github.com/rancher/websocket-proxy/proxy/frontend_http_handler.go @@ -2,9 +2,11 @@ package proxy import ( "bufio" + "encoding/base64" "errors" "io" "net/http" + "net/url" log "github.com/Sirupsen/logrus" "github.com/dgrijalva/jwt-go" @@ -27,13 +29,15 @@ func (h *FrontendHTTPHandler) ServeHTTP(rw http.ResponseWriter, req *http.Reques } func (h *FrontendHTTPHandler) serveHTTP(rw http.ResponseWriter, req *http.Request) error { - token, hostKey, authed, err := h.authAndLookup(req) - if err != nil { - http.Error(rw, "Service Unavailable", 503) + token, hostKey, err := h.authAndLookup(req) + if IsNoAuthError(err) { + redirect := *req.URL + redirect.RawQuery = "redirectTo=" + url.QueryEscape(req.URL.Path) + "#" + redirect.Path = "/login" + http.Redirect(rw, req, redirect.String(), 302) return nil - } - if !authed { - http.Error(rw, "Failed authentication", 401) + } else if err != nil { + http.Error(rw, "Service unavailable", 503) return nil } @@ -52,6 +56,8 @@ func (h *FrontendHTTPHandler) serveHTTP(rw http.ResponseWriter, req *http.Reques defer writer.Close() defer reader.Close() + h.copyAuthHeaders(req) + hijack := h.shouldHijack(req) if err := writer.WriteRequest(req, hijack, address, scheme); err != nil { @@ -91,6 +97,24 @@ func (h *FrontendHTTPHandler) serveHTTP(rw http.ResponseWriter, req *http.Reques return err } +func (h *FrontendHTTPHandler) copyAuthHeaders(req *http.Request) { + c, err := req.Cookie("token") + if err != nil { + c = nil + } + + authHeader := req.Header.Get("Authorization") + if authHeader != "" { + return + } + + tokenValue := "unauthorized" + if c != nil { + tokenValue = c.Value + } + req.Header.Set("Authorization", "Bearer "+base64.StdEncoding.EncodeToString([]byte("Bearer "+tokenValue))) +} + type flusher struct { writer io.Writer } @@ -112,37 +136,33 @@ func (h *FrontendHTTPHandler) shouldHijack(req *http.Request) bool { return req.Header.Get("Connection") == "Upgrade" } -func (h *FrontendHTTPHandler) authAndLookup(req *http.Request) (*jwt.Token, string, bool, error) { - token, hostKey, authErr := h.FrontendHandler.auth(req) - if authErr == nil { - return token, hostKey, true, nil - } else if !IsNoTokenError(authErr) { - log.Infof("Frontend auth failed: %v. Getting new token.", authErr) +func (h *FrontendHTTPHandler) authAndLookup(req *http.Request) (*jwt.Token, string, error) { + token, hostKey, err := h.FrontendHandler.auth(req) + if err == nil { + return token, hostKey, nil } tokenString, err := h.TokenLookup.Lookup(req) if err != nil { log.WithFields(log.Fields{"error": err}).Error("Error looking up token.") - return nil, "", false, err + return nil, "", err } token, err = jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { return h.parsedPublicKey, nil }) if err != nil { - return nil, "", false, err - } - - if !token.Valid { - return nil, "", false, nil + return nil, "", err + } else if !token.Valid { + return nil, "", noAuthError{err: "Token is not valid"} } hostUUID, found := token.Claims["hostUuid"] if found { if hostKey, ok := hostUUID.(string); ok && h.backend.hasBackend(hostKey) { - return token, hostKey, true, nil + return token, hostKey, nil } } log.WithFields(log.Fields{"hostUuid": hostUUID}).Infof("Invalid backend host requested.") - return nil, "", false, nil + return nil, "", errors.New("invalid backend") } diff --git a/vendor/github.com/rancher/websocket-proxy/proxy/proxy.go b/vendor/github.com/rancher/websocket-proxy/proxy/proxy.go index 195d6992..2419c56e 100644 --- a/vendor/github.com/rancher/websocket-proxy/proxy/proxy.go +++ b/vendor/github.com/rancher/websocket-proxy/proxy/proxy.go @@ -86,7 +86,7 @@ func (s *Starter) StartProxy() error { router.Handle(p, frontendHandler).Methods("GET") } for _, p := range s.FrontendHTTPPaths { - router.Handle(p, frontendHTTPHandler).Methods("GET", "POST", "PUT", "DELETE", "PATCH") + router.Handle(p, frontendHTTPHandler).Methods("GET", "POST", "PUT", "DELETE", "PATCH", "HEAD") } for _, p := range s.StatsPaths { router.Handle(p, statsHandler).Methods("GET") diff --git a/vendor/github.com/rancher/websocket-proxy/proxy/stats_handler.go b/vendor/github.com/rancher/websocket-proxy/proxy/stats_handler.go index ef548f92..c86b6976 100644 --- a/vendor/github.com/rancher/websocket-proxy/proxy/stats_handler.go +++ b/vendor/github.com/rancher/websocket-proxy/proxy/stats_handler.go @@ -54,8 +54,8 @@ func (h *StatsHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { multiHost = true } - statsInfoStructs, authErr := h.auth(req, multiHost) - if authErr != nil { + tokenString, authToken, err := h.auth(req) + if err != nil { http.Error(rw, "Failed authentication", 401) return } @@ -69,6 +69,23 @@ func (h *StatsHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { return } + if ok, _ := authToken.Claims["payload"].(bool); ok { + ws.SetReadDeadline(time.Now().Add(30 * time.Second)) + _, content, err := ws.ReadMessage() + if err != nil { + http.Error(rw, "Failed to read payload", 500) + return + } + tokenString = string(content) + } + + statsInfoStructs, err := h.parseStatsInfo(req, tokenString, multiHost) + if err != nil { + log.Error("Failed to read statsinfo", err) + http.Error(rw, "Failed to read payload targets", 500) + return + } + var mutex sync.Mutex var countMutex sync.Mutex @@ -127,8 +144,21 @@ func (h *StatsHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } } -func (h *StatsHandler) auth(req *http.Request, multiHost bool) ([]*statsInfo, error) { +func (h *StatsHandler) auth(req *http.Request) (string, *jwt.Token, error) { tokenString := req.URL.Query().Get("token") + token, err := parseRequestToken(tokenString, h.parsedPublicKey) + if err != nil { + return "", nil, fmt.Errorf("Error parsing stats token. Failing auth. Error: %v", err) + } + + if !token.Valid { + return "", nil, fmt.Errorf("Token not valid") + } + + return tokenString, token, nil +} + +func (h *StatsHandler) parseStatsInfo(req *http.Request, tokenString string, multiHost bool) ([]*statsInfo, error) { token, err := parseRequestToken(tokenString, h.parsedPublicKey) if err != nil { return nil, fmt.Errorf("Error parsing stats token. Failing auth. Error: %v", err) @@ -190,12 +220,8 @@ func getProjectOrService(token *jwt.Token) ([]map[string]string, error) { if ok { projectMap := map[string]string{} for key, value := range projectInterfaceMap { - valueString, ok := value.(string) - if ok { - projectMap[key] = valueString - } else { - return nil, fmt.Errorf("invalid project/service input data type") - } + valueString, _ := value.(string) + projectMap[key] = valueString } projectList = append(projectList, projectMap) } else { diff --git a/vendor/github.com/rancher/websocket-proxy/proxy/token.go b/vendor/github.com/rancher/websocket-proxy/proxy/token.go index 7499c6dd..e1da180e 100644 --- a/vendor/github.com/rancher/websocket-proxy/proxy/token.go +++ b/vendor/github.com/rancher/websocket-proxy/proxy/token.go @@ -3,6 +3,7 @@ package proxy import ( "bytes" "crypto/sha256" + "encoding/base64" "encoding/hex" "encoding/json" "fmt" @@ -33,7 +34,7 @@ type TokenLookup struct { func NewTokenLookup(cattleAddr string) *TokenLookup { t := &TokenLookup{ - cache: cache.New(5*time.Minute, 30*time.Second), + cache: cache.New(30*time.Second, 30*time.Second), serviceProxyURL: fmt.Sprintf("http://%s/v1/serviceproxies", cattleAddr), } t.client.Timeout = 60 * time.Second @@ -76,17 +77,23 @@ func (t *TokenLookup) callRancher(r *http.Request) (string, error) { parts := strings.SplitN(service, ":", 2) port := 80 + scheme := "http" if len(parts) == 2 { var err error port, err = strconv.Atoi(parts[1]) if err != nil { return "", err } + + if strings.HasSuffix(parts[1], "443") { + scheme = "https" + } } body, err := json.Marshal(&ServiceProxyRequest{ Service: parts[0], Port: port, + Scheme: scheme, }) logrus.Debugf("Calling rancher to get token: %s", t.serviceProxyURL) @@ -103,9 +110,8 @@ func (t *TokenLookup) callRancher(r *http.Request) (string, error) { newReq.Header.Set("X-API-Client-Access-Key", r.TLS.PeerCertificates[0].Subject.CommonName) } else { // Other forms of auth - for _, k := range []string{authHeader, projectHeader} { - newReq.Header.Set(k, r.Header.Get(k)) - } + newReq.Header.Set(authHeader, unwrapAuth(r.Header.Get(authHeader))) + newReq.Header.Set(projectHeader, r.Header.Get(projectHeader)) if project, ok := vars["project"]; ok { newReq.Header.Set(projectHeader, project) @@ -123,7 +129,9 @@ func (t *TokenLookup) callRancher(r *http.Request) (string, error) { } defer resp.Body.Close() - if resp.StatusCode >= 400 { + if resp.StatusCode == 401 { + return "", noAuthError{} + } else if resp.StatusCode >= 400 { return "", fmt.Errorf("HTTP error: %s, %d", resp.Status, resp.StatusCode) } @@ -135,6 +143,19 @@ func (t *TokenLookup) callRancher(r *http.Request) (string, error) { return respBody.Token, nil } +func unwrapAuth(auth string) string { + if !strings.HasPrefix(auth, "Bearer ") { + return auth + } + + if tmp, err := base64.StdEncoding.DecodeString(auth[7:]); err == nil && len(strings.Split(string(tmp), " ")) == 2 { + // This is double base64 auth encoding that we do w/ k8s + return string(tmp) + } + + return auth +} + func genKey(r *http.Request) string { vars := mux.Vars(r) hash := sha256.New() @@ -167,6 +188,7 @@ func writeHeader(h hash.Hash, key string, r *http.Request) { type ServiceProxyRequest struct { Service string `json:"service"` Port int `json:"port"` + Scheme string `json:"scheme"` } type ServiceProxyResponse struct {