// writeError logs and writes the given Error to the ResponseWriter // If Error is an etcdErr, it is rendered to the ResponseWriter // Otherwise, it is assumed to be a StatusInternalServerError func writeError(w http.ResponseWriter, r *http.Request, err error) { if err == nil { return } switch e := err.(type) { case *etcdErr.Error: e.WriteTo(w) case *httptypes.HTTPError: if et := e.WriteTo(w); et != nil { plog.Debugf("error writing HTTPError (%v) to %s", et, r.RemoteAddr) } case auth.Error: herr := httptypes.NewHTTPError(e.HTTPStatus(), e.Error()) if et := herr.WriteTo(w); et != nil { plog.Debugf("error writing HTTPError (%v) to %s", et, r.RemoteAddr) } default: switch err { case etcdserver.ErrTimeoutDueToLeaderFail, etcdserver.ErrTimeoutDueToConnectionLost, etcdserver.ErrNotEnoughStartedMembers: mlog.MergeError(err) default: mlog.MergeErrorf("got unexpected response error (%v)", err) } herr := httptypes.NewHTTPError(http.StatusInternalServerError, "Internal Server Error") if et := herr.WriteTo(w); et != nil { plog.Debugf("error writing HTTPError (%v) to %s", et, r.RemoteAddr) } } }
func unmarshalRequest(r *http.Request, req json.Unmarshaler, w http.ResponseWriter) bool { ctype := r.Header.Get("Content-Type") if ctype != "application/json" { writeError(w, r, httptypes.NewHTTPError(http.StatusUnsupportedMediaType, fmt.Sprintf("Bad Content-Type %s, accept application/json", ctype))) return false } b, err := ioutil.ReadAll(r.Body) if err != nil { writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, err.Error())) return false } if err := req.UnmarshalJSON(b); err != nil { writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, err.Error())) return false } return true }
func (s *EtcdServer) Process(ctx context.Context, m raftpb.Message) error { if s.cluster.IsIDRemoved(types.ID(m.From)) { plog.Warningf("reject message from removed member %s", types.ID(m.From).String()) return httptypes.NewHTTPError(http.StatusForbidden, "cannot process message from removed member") } if m.Type == raftpb.MsgApp { s.stats.RecvAppendReq(types.ID(m.From).String(), m.Size()) } return s.r.Step(ctx, m) }
func unmarshalRequest(r *http.Request, req json.Unmarshaler, w http.ResponseWriter) bool { ctype := r.Header.Get("Content-Type") semicolonPosition := strings.Index(ctype, ";") if semicolonPosition != -1 { ctype = strings.TrimSpace(strings.ToLower(ctype[0:semicolonPosition])) } if ctype != "application/json" { writeError(w, r, httptypes.NewHTTPError(http.StatusUnsupportedMediaType, fmt.Sprintf("Bad Content-Type %s, accept application/json", ctype))) return false } b, err := ioutil.ReadAll(r.Body) if err != nil { writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, err.Error())) return false } if err := req.UnmarshalJSON(b); err != nil { writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, err.Error())) return false } return true }
func (h *statsHandler) serveLeader(w http.ResponseWriter, r *http.Request) { if !allowMethod(w, r.Method, "GET") { return } stats := h.stats.LeaderStats() if stats == nil { writeError(w, r, httptypes.NewHTTPError(http.StatusForbidden, "not current leader")) return } w.Header().Set("Content-Type", "application/json") w.Write(stats) }
func getID(p string, w http.ResponseWriter) (types.ID, bool) { idStr := trimPrefix(p, membersPrefix) if idStr == "" { http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) return 0, false } id, err := types.IDFromString(idStr) if err != nil { writeError(w, nil, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", idStr))) return 0, false } return id, true }
func logHandleFunc(w http.ResponseWriter, r *http.Request) { if !allowMethod(w, r.Method, "PUT") { return } in := struct{ Level string }{} d := json.NewDecoder(r.Body) if err := d.Decode(&in); err != nil { writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid json body")) return } logl, err := capnslog.ParseLevel(strings.ToUpper(in.Level)) if err != nil { writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid log level "+in.Level)) return } plog.Noticef("globalLogLevel set to %q", logl.String()) capnslog.SetGlobalLogLevel(logl) w.WriteHeader(http.StatusNoContent) }
func (sh *authHandler) handleUsers(w http.ResponseWriter, r *http.Request) { subpath := path.Clean(r.URL.Path[len(authPrefix):]) // Split "/users/username". // First item is an empty string, second is "users" pieces := strings.Split(subpath, "/") if len(pieces) == 2 { sh.baseUsers(w, r) return } if len(pieces) != 3 { writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid path")) return } sh.forUser(w, r, pieces[2]) }
func notCapable(w http.ResponseWriter, r *http.Request, c capability) { herr := httptypes.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Not capable of accessing %s feature during rolling upgrades.", c)) if err := herr.WriteTo(w); err != nil { plog.Debugf("error writing HTTPError (%v) to %s", err, r.RemoteAddr) } }
func (p *reverseProxy) ServeHTTP(rw http.ResponseWriter, clientreq *http.Request) { proxyreq := new(http.Request) *proxyreq = *clientreq startTime := time.Now() var ( proxybody []byte err error ) if clientreq.Body != nil { proxybody, err = ioutil.ReadAll(clientreq.Body) if err != nil { msg := fmt.Sprintf("proxy: failed to read request body: %v", err) e := httptypes.NewHTTPError(http.StatusInternalServerError, msg) if we := e.WriteTo(rw); we != nil { plog.Debugf("error writing HTTPError (%v) to %s", we, clientreq.RemoteAddr) } return } } // deep-copy the headers, as these will be modified below proxyreq.Header = make(http.Header) copyHeader(proxyreq.Header, clientreq.Header) normalizeRequest(proxyreq) removeSingleHopHeaders(&proxyreq.Header) maybeSetForwardedFor(proxyreq) endpoints := p.director.endpoints() if len(endpoints) == 0 { msg := "proxy: zero endpoints currently available" reportRequestDropped(clientreq, zeroEndpoints) // TODO: limit the rate of the error logging. log.Printf(msg) e := httptypes.NewHTTPError(http.StatusServiceUnavailable, msg) if we := e.WriteTo(rw); we != nil { plog.Debugf("error writing HTTPError (%v) to %s", we, clientreq.RemoteAddr) } return } var requestClosed int32 completeCh := make(chan bool, 1) closeNotifier, ok := rw.(http.CloseNotifier) cancel := httputil.RequestCanceler(p.transport, proxyreq) if ok { go func() { select { case <-closeNotifier.CloseNotify(): atomic.StoreInt32(&requestClosed, 1) log.Printf("proxy: client %v closed request prematurely", clientreq.RemoteAddr) cancel() case <-completeCh: } }() defer func() { completeCh <- true }() } var res *http.Response for _, ep := range endpoints { if proxybody != nil { proxyreq.Body = ioutil.NopCloser(bytes.NewBuffer(proxybody)) } redirectRequest(proxyreq, ep.URL) res, err = p.transport.RoundTrip(proxyreq) if atomic.LoadInt32(&requestClosed) == 1 { return } if err != nil { reportRequestDropped(clientreq, failedSendingRequest) log.Printf("proxy: failed to direct request to %s: %v", ep.URL.String(), err) ep.Failed() continue } break } if res == nil { // TODO: limit the rate of the error logging. msg := fmt.Sprintf("proxy: unable to get response from %d endpoint(s)", len(endpoints)) reportRequestDropped(clientreq, failedGettingResponse) log.Printf(msg) e := httptypes.NewHTTPError(http.StatusBadGateway, msg) if we := e.WriteTo(rw); we != nil { plog.Debugf("error writing HTTPError (%v) to %s", we, clientreq.RemoteAddr) } return } defer res.Body.Close() reportRequestHandled(clientreq, res, startTime) removeSingleHopHeaders(&res.Header) copyHeader(rw.Header(), res.Header) rw.WriteHeader(res.StatusCode) io.Copy(rw, res.Body) }
func (sh *authHandler) forUser(w http.ResponseWriter, r *http.Request, user string) { if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") { return } if !hasRootAccess(sh.sec, r, sh.clientCertAuthEnabled) { writeNoAuth(w, r) return } w.Header().Set("X-Etcd-Cluster-ID", sh.cluster.ID().String()) w.Header().Set("Content-Type", "application/json") switch r.Method { case "GET": u, err := sh.sec.GetUser(user) if err != nil { writeError(w, r, err) return } err = r.ParseForm() if err != nil { writeError(w, r, err) return } uwr := userWithRoles{User: u.User} for _, roleName := range u.Roles { var role auth.Role role, err = sh.sec.GetRole(roleName) if err != nil { writeError(w, r, err) return } uwr.Roles = append(uwr.Roles, role) } err = json.NewEncoder(w).Encode(uwr) if err != nil { plog.Warningf("forUser error encoding on %s", r.URL) return } return case "PUT": var u auth.User err := json.NewDecoder(r.Body).Decode(&u) if err != nil { writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid JSON in request body.")) return } if u.User != user { writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "User JSON name does not match the name in the URL")) return } var ( out auth.User created bool ) if len(u.Grant) == 0 && len(u.Revoke) == 0 { // create or update if len(u.Roles) != 0 { out, err = sh.sec.CreateUser(u) } else { // if user passes in both password and roles, we are unsure about his/her // intention. out, created, err = sh.sec.CreateOrUpdateUser(u) } if err != nil { writeError(w, r, err) return } } else { // update case if len(u.Roles) != 0 { writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "User JSON contains both roles and grant/revoke")) return } out, err = sh.sec.UpdateUser(u) if err != nil { writeError(w, r, err) return } } if created { w.WriteHeader(http.StatusCreated) } else { w.WriteHeader(http.StatusOK) } out.Password = "" err = json.NewEncoder(w).Encode(out) if err != nil { plog.Warningf("forUser error encoding on %s", r.URL) return } return case "DELETE": err := sh.sec.DeleteUser(user) if err != nil { writeError(w, r, err) return } } }
func (sh *authHandler) forRole(w http.ResponseWriter, r *http.Request, role string) { if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") { return } if !hasRootAccess(sh.sec, r, sh.clientCertAuthEnabled) { writeNoAuth(w, r) return } w.Header().Set("X-Etcd-Cluster-ID", sh.cluster.ID().String()) w.Header().Set("Content-Type", "application/json") switch r.Method { case "GET": data, err := sh.sec.GetRole(role) if err != nil { writeError(w, r, err) return } err = json.NewEncoder(w).Encode(data) if err != nil { plog.Warningf("forRole error encoding on %s", r.URL) return } return case "PUT": var in auth.Role err := json.NewDecoder(r.Body).Decode(&in) if err != nil { writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid JSON in request body.")) return } if in.Role != role { writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Role JSON name does not match the name in the URL")) return } var out auth.Role // create if in.Grant.IsEmpty() && in.Revoke.IsEmpty() { err = sh.sec.CreateRole(in) if err != nil { writeError(w, r, err) return } w.WriteHeader(http.StatusCreated) out = in } else { if !in.Permissions.IsEmpty() { writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Role JSON contains both permissions and grant/revoke")) return } out, err = sh.sec.UpdateRole(in) if err != nil { writeError(w, r, err) return } w.WriteHeader(http.StatusOK) } err = json.NewEncoder(w).Encode(out) if err != nil { plog.Warningf("forRole error encoding on %s", r.URL) return } return case "DELETE": err := sh.sec.DeleteRole(role) if err != nil { writeError(w, r, err) return } } }
func writeNoAuth(w http.ResponseWriter, r *http.Request) { herr := httptypes.NewHTTPError(http.StatusUnauthorized, "Insufficient credentials") if err := herr.WriteTo(w); err != nil { plog.Debugf("error writing HTTPError (%v) to %s", err, r.RemoteAddr) } }
func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if !allowMethod(w, r.Method, "GET", "POST", "DELETE", "PUT") { return } if !hasWriteRootAccess(h.sec, r, h.clientCertAuthEnabled) { writeNoAuth(w, r) return } w.Header().Set("X-Etcd-Cluster-ID", h.cluster.ID().String()) ctx, cancel := context.WithTimeout(context.Background(), h.timeout) defer cancel() switch r.Method { case "GET": switch trimPrefix(r.URL.Path, membersPrefix) { case "": mc := newMemberCollection(h.cluster.Members()) w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(mc); err != nil { plog.Warningf("failed to encode members response (%v)", err) } case "leader": id := h.server.Leader() if id == 0 { writeError(w, r, httptypes.NewHTTPError(http.StatusServiceUnavailable, "During election")) return } m := newMember(h.cluster.Member(id)) w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(m); err != nil { plog.Warningf("failed to encode members response (%v)", err) } default: writeError(w, r, httptypes.NewHTTPError(http.StatusNotFound, "Not found")) } case "POST": req := httptypes.MemberCreateRequest{} if ok := unmarshalRequest(r, &req, w); !ok { return } now := h.clock.Now() m := membership.NewMember("", req.PeerURLs, "", &now) err := h.server.AddMember(ctx, *m) switch { case err == membership.ErrIDExists || err == membership.ErrPeerURLexists: writeError(w, r, httptypes.NewHTTPError(http.StatusConflict, err.Error())) return case err != nil: plog.Errorf("error adding member %s (%v)", m.ID, err) writeError(w, r, err) return } res := newMember(m) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) if err := json.NewEncoder(w).Encode(res); err != nil { plog.Warningf("failed to encode members response (%v)", err) } case "DELETE": id, ok := getID(r.URL.Path, w) if !ok { return } err := h.server.RemoveMember(ctx, uint64(id)) switch { case err == membership.ErrIDRemoved: writeError(w, r, httptypes.NewHTTPError(http.StatusGone, fmt.Sprintf("Member permanently removed: %s", id))) case err == membership.ErrIDNotFound: writeError(w, r, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", id))) case err != nil: plog.Errorf("error removing member %s (%v)", id, err) writeError(w, r, err) default: w.WriteHeader(http.StatusNoContent) } case "PUT": id, ok := getID(r.URL.Path, w) if !ok { return } req := httptypes.MemberUpdateRequest{} if ok := unmarshalRequest(r, &req, w); !ok { return } m := membership.Member{ ID: id, RaftAttributes: membership.RaftAttributes{PeerURLs: req.PeerURLs.StringSlice()}, } err := h.server.UpdateMember(ctx, m) switch { case err == membership.ErrPeerURLexists: writeError(w, r, httptypes.NewHTTPError(http.StatusConflict, err.Error())) case err == membership.ErrIDNotFound: writeError(w, r, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", id))) case err != nil: plog.Errorf("error updating member %s (%v)", m.ID, err) writeError(w, r, err) default: w.WriteHeader(http.StatusNoContent) } } }