package integration import ( "crypto/tls" "encoding/base64" "encoding/json" "io" "net/http" "net/http/httputil" "net/url" "strings" "github.com/golang/glog" "github.com/gorilla/context" knet "k8s.io/kubernetes/pkg/util/net" ) type User struct { ID string Password string Name string Email string } type BasicAuthChallenger struct { realm string users map[string]User authenticatedHandler http.Handler } // NewBasicAuthChallenger provides a simple basic auth server that is compatible with our basic auth password validator func NewBasicAuthChallenger(realm string, users []User, authenticatedHandler http.Handler) BasicAuthChallenger { userMap := make(map[string]User, len(users)) for _, user := range users { userMap[user.ID] = user } return BasicAuthChallenger{realm, userMap, authenticatedHandler} } func (challenger BasicAuthChallenger) ServeHTTP(w http.ResponseWriter, r *http.Request) { glog.Infof("--- %v\n", r.URL) authHeader := r.Header["Authorization"] if len(authHeader) == 0 { challenger.Challenge(w) return } auth := strings.SplitN(authHeader[0], " ", 2) if len(auth) != 2 || auth[0] != "Basic" { Error("bad syntax", http.StatusBadRequest, w) return } payload, err := base64.StdEncoding.DecodeString(auth[1]) if err != nil { Error("bad syntax", http.StatusBadRequest, w) return } pair := strings.SplitN(string(payload), ":", 2) if len(pair) != 2 { Error("bad syntax", http.StatusBadRequest, w) return } if !challenger.Validate(pair[0], pair[1]) { challenger.Challenge(w) return } context.Set(r, "username", challenger.users[pair[0]]) challenger.authenticatedHandler.ServeHTTP(w, r) } func (challenger *BasicAuthChallenger) Challenge(w http.ResponseWriter) { glog.Infof("Sending challenge\n") w.Header().Set("WWW-Authenticate", "Basic realm=\""+challenger.realm+"\"") Error("Authorization failed", http.StatusUnauthorized, w) } func Error(message string, status int, w http.ResponseWriter) { glog.Infof("Writing error: %s\n", message) data := map[string]string{"error": message} json, _ := json.Marshal(data) w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) io.WriteString(w, string(json)) } func (challenger *BasicAuthChallenger) Validate(username, password string) bool { knownUser, exists := challenger.users[username] if !exists { return false } if knownUser.Password == password { glog.Infof("Validated user: %s\n", username) return true } glog.Infof("Rejected user: %s\n", username) return false } type identifyingHandler struct { } func (handler *identifyingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { user := context.Get(r, "username") if user == nil { Error("No user found", http.StatusBadRequest, w) return } w.Header().Set("Content-Type", "application/json") result, _ := json.Marshal(user) io.WriteString(w, string(result)) } func NewIdentifyingHandler() http.Handler { return &identifyingHandler{} } type xRemoteUserProxyingHandler struct { proxier *httputil.ReverseProxy } func (handler *xRemoteUserProxyingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { user := context.Get(r, "username") if user == nil { Error("No user found", http.StatusBadRequest, w) return } r.Header.Add("X-Remote-User", user.(User).ID) handler.proxier.ServeHTTP(w, r) } func NewXRemoteUserProxyingHandler(rawURL string) http.Handler { parsedURL, _ := url.Parse(rawURL) proxier := httputil.NewSingleHostReverseProxy(parsedURL) proxier.Transport = insecureTransport() // proxier.Transport = NewBasicAuthRoundTripper(http.DefaultTransport) return &xRemoteUserProxyingHandler{proxier} } func insecureTransport() *http.Transport { return knet.SetTransportDefaults(&http.Transport{ TLSClientConfig: &tls.Config{ // Change default from SSLv3 to TLSv1.0 (because of POODLE vulnerability) MinVersion: tls.VersionTLS10, InsecureSkipVerify: true, }, }) }