func (sh *securityHandler) 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.clusterInfo.ID().String()) switch r.Method { case "GET": w.Header().Set("Content-Type", "application/json") u, err := sh.sec.GetUser(user) if err != nil { writeError(w, err) return } u.Password = "" err = json.NewEncoder(w).Encode(u) if err != nil { log.Println("etcdhttp: forUser error encoding on", r.URL) return } return case "PUT": var u security.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(400, "User JSON name does not match the name in the URL")) return } newuser, err := sh.sec.CreateOrUpdateUser(u) if err != nil { writeError(w, err) return } w.WriteHeader(http.StatusCreated) err = json.NewEncoder(w).Encode(newuser) if err != nil { log.Println("etcdhttp: forUser error encoding on", r.URL) return } return case "DELETE": err := sh.sec.DeleteUser(user) if err != nil { writeError(w, err) return } } }
func (p *reverseProxy) ServeHTTP(rw http.ResponseWriter, clientreq *http.Request) { proxyreq := new(http.Request) *proxyreq = *clientreq // 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" // TODO: limit the rate of the error logging. log.Printf(msg) e := httptypes.NewHTTPError(http.StatusServiceUnavailable, msg) e.WriteTo(rw) return } var res *http.Response var err error for _, ep := range endpoints { redirectRequest(proxyreq, ep.URL) res, err = p.transport.RoundTrip(proxyreq) if err != nil { 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)) log.Printf(msg) e := httptypes.NewHTTPError(http.StatusBadGateway, msg) e.WriteTo(rw) return } defer res.Body.Close() removeSingleHopHeaders(&res.Header) copyHeader(rw.Header(), res.Header) rw.WriteHeader(res.StatusCode) io.Copy(rw, res.Body) }
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 }
// 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 security.MergeError: herr := httptypes.NewHTTPError(http.StatusBadRequest, e.Error()) herr.WriteTo(w) default: log.Printf("etcdhttp: unexpected error: %v", err) herr := httptypes.NewHTTPError(http.StatusInternalServerError, "Internal Server Error") herr.WriteTo(w) } }
func (s *EtcdServer) Process(ctx context.Context, m raftpb.Message) error { if s.Cluster.IsIDRemoved(types.ID(m.From)) { log.Printf("etcdserver: 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 (sh *securityHandler) enableDisable(w http.ResponseWriter, r *http.Request) { if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") { return } if !hasWriteRootAccess(sh.sec, r) { writeNoAuth(w) return } w.Header().Set("X-Etcd-Cluster-ID", sh.clusterInfo.ID().String()) w.Header().Set("Content-Type", "application/json") isEnabled := sh.sec.SecurityEnabled() switch r.Method { case "GET": jsonDict := enabled{isEnabled} err := json.NewEncoder(w).Encode(jsonDict) if err != nil { log.Println("etcdhttp: error encoding security state on", r.URL) } case "PUT": var in security.User err := json.NewDecoder(r.Body).Decode(&in) if err != nil { writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid JSON in request body.")) return } if in.User != "root" { writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, "Need to create root user")) return } err = sh.sec.EnableSecurity(in) if err != nil { writeError(w, err) return } case "DELETE": err := sh.sec.DisableSecurity() if err != nil { writeError(w, err) return } } }
func (sh *securityHandler) handleUsers(w http.ResponseWriter, r *http.Request) { subpath := path.Clean(r.URL.Path[len(securityPrefix):]) // Split "/users/username/command". // First item is an empty string, second is "users" pieces := strings.Split(subpath, "/") if len(pieces) != 3 { writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid path")) return } sh.forUser(w, r, pieces[2]) }
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 }
func writeNoAuth(w http.ResponseWriter) { herr := httptypes.NewHTTPError(http.StatusUnauthorized, "Insufficient credentials") herr.WriteTo(w) }
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) { writeNoAuth(w) 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) } } }