func unmarshalRequest(r *http.Request, req json.Unmarshaler, w http.ResponseWriter) bool { ctype := r.Header.Get("Content-Type") if ctype != "application/json" { writeError(w, 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, httptypes.NewHTTPError(http.StatusBadRequest, err.Error())) return false } if err := req.UnmarshalJSON(b); err != nil { writeError(w, 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 (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, 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, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", idStr))) return 0, false } return id, true }
// 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 an InternalServerError func writeError(w http.ResponseWriter, err error) { if err == nil { return } switch e := err.(type) { case *etcdErr.Error: e.WriteTo(w) case *httptypes.HTTPError: e.WriteTo(w) case auth.Error: herr := httptypes.NewHTTPError(e.HTTPStatus(), e.Error()) herr.WriteTo(w) default: if err == etcdserver.ErrTimeoutDueToLeaderFail { plog.Error(err) } else { plog.Errorf("got unexpected response error (%v)", err) } herr := httptypes.NewHTTPError(http.StatusInternalServerError, "Internal Server Error") herr.WriteTo(w) } }
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, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid json body")) return } logl, err := capnslog.ParseLevel(strings.ToUpper(in.Level)) if err != nil { writeError(w, 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, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid path")) return } sh.forUser(w, r, pieces[2]) }
// 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 an InternalServerError func writeError(w http.ResponseWriter, err error) { if err == nil { return } switch e := err.(type) { case *etcdErr.Error: e.WriteTo(w) case *httptypes.HTTPError: e.WriteTo(w) default: log.Printf("etcdhttp: unexpected error: %v", err) herr := httptypes.NewHTTPError(http.StatusInternalServerError, "Internal Server Error") herr.WriteTo(w) } }
func notCapable(w http.ResponseWriter, c capability) { herr := httptypes.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Not capable of accessing %s feature during rolling upgrades.", c)) herr.WriteTo(w) }
func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if !allowMethod(w, r.Method, "GET", "POST", "DELETE", "PUT") { return } w.Header().Set("X-Etcd-Cluster-ID", h.clusterInfo.ID().String()) ctx, cancel := context.WithTimeout(context.Background(), defaultServerTimeout) defer cancel() switch r.Method { case "GET": switch trimPrefix(r.URL.Path, membersPrefix) { case "": mc := newMemberCollection(h.clusterInfo.Members()) w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(mc); err != nil { log.Printf("etcdhttp: %v", err) } case "leader": id := h.server.Leader() if id == 0 { writeError(w, httptypes.NewHTTPError(http.StatusServiceUnavailable, "During election")) return } m := newMember(h.clusterInfo.Member(id)) w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(m); err != nil { log.Printf("etcdhttp: %v", err) } default: writeError(w, httptypes.NewHTTPError(http.StatusNotFound, "Not found")) } case "POST": req := httptypes.MemberCreateRequest{} if ok := unmarshalRequest(r, &req, w); !ok { return } now := h.clock.Now() m := etcdserver.NewMember("", req.PeerURLs, "", &now) err := h.server.AddMember(ctx, *m) switch { case err == etcdserver.ErrIDExists || err == etcdserver.ErrPeerURLexists: writeError(w, httptypes.NewHTTPError(http.StatusConflict, err.Error())) return case err != nil: log.Printf("etcdhttp: error adding node %s: %v", m.ID, err) writeError(w, 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 { log.Printf("etcdhttp: %v", err) } case "DELETE": id, ok := getID(r.URL.Path, w) if !ok { return } err := h.server.RemoveMember(ctx, uint64(id)) switch { case err == etcdserver.ErrIDRemoved: writeError(w, httptypes.NewHTTPError(http.StatusGone, fmt.Sprintf("Member permanently removed: %s", id))) case err == etcdserver.ErrIDNotFound: writeError(w, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", id))) case err != nil: log.Printf("etcdhttp: error removing node %s: %v", id, err) writeError(w, 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 := etcdserver.Member{ ID: id, RaftAttributes: etcdserver.RaftAttributes{PeerURLs: req.PeerURLs.StringSlice()}, } err := h.server.UpdateMember(ctx, m) switch { case err == etcdserver.ErrPeerURLexists: writeError(w, httptypes.NewHTTPError(http.StatusConflict, err.Error())) case err == etcdserver.ErrIDNotFound: writeError(w, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", id))) case err != nil: log.Printf("etcdhttp: error updating node %s: %v", m.ID, err) writeError(w, err) default: w.WriteHeader(http.StatusNoContent) } } }
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) { writeNoAuth(w) 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, err) return } u.Password = "" err = json.NewEncoder(w).Encode(u) 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, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid JSON in request body.")) return } if u.User != user { writeError(w, 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, err) return } } else { // update case if len(u.Roles) != 0 { writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, "User JSON contains both roles and grant/revoke")) return } out, err = sh.sec.UpdateUser(u) if err != nil { writeError(w, 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, 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) { writeNoAuth(w) 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, 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, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid JSON in request body.")) return } if in.Role != role { writeError(w, 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, err) return } w.WriteHeader(http.StatusCreated) out = in } else { if !in.Permissions.IsEmpty() { writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, "Role JSON contains both permissions and grant/revoke")) return } out, err = sh.sec.UpdateRole(in) if err != nil { writeError(w, 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, err) return } } }
func writeNoAuth(w http.ResponseWriter) { herr := httptypes.NewHTTPError(http.StatusUnauthorized, "Insufficient credentials") herr.WriteTo(w) }