181 lines
4.4 KiB
Go
Raw Permalink Normal View History

2025-02-21 13:10:59 -08:00
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package tsconsensus
import (
"bytes"
"context"
"encoding/json"
2025-02-27 08:47:24 -08:00
"errors"
"fmt"
"io"
2025-02-27 05:15:49 -08:00
"log"
"net/http"
"time"
2025-02-21 13:38:53 -08:00
"tailscale.com/util/httpm"
)
type joinRequest struct {
RemoteHost string
RemoteID string
}
type commandClient struct {
port uint16
httpClient *http.Client
}
2025-02-26 15:03:37 -08:00
func (rac *commandClient) url(host string, path string) string {
return fmt.Sprintf("http://%s:%d%s", host, rac.port, path)
}
func (rac *commandClient) join(host string, jr joinRequest) error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
rBs, err := json.Marshal(jr)
if err != nil {
return err
}
2025-02-26 15:03:37 -08:00
url := rac.url(host, "/join")
2025-02-21 13:38:53 -08:00
req, err := http.NewRequestWithContext(ctx, httpm.POST, url, bytes.NewReader(rBs))
if err != nil {
return err
}
resp, err := rac.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
respBs, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != 200 {
return fmt.Errorf("remote responded %d: %s", resp.StatusCode, string(respBs))
}
return nil
}
func (rac *commandClient) executeCommand(host string, bs []byte) (CommandResult, error) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
2025-02-26 15:03:37 -08:00
url := rac.url(host, "/executeCommand")
2025-02-21 13:38:53 -08:00
req, err := http.NewRequestWithContext(ctx, httpm.POST, url, bytes.NewReader(bs))
if err != nil {
return CommandResult{}, err
}
resp, err := rac.httpClient.Do(req)
if err != nil {
return CommandResult{}, err
}
defer resp.Body.Close()
respBs, err := io.ReadAll(resp.Body)
if err != nil {
return CommandResult{}, err
}
if resp.StatusCode != 200 {
return CommandResult{}, fmt.Errorf("remote responded %d: %s", resp.StatusCode, string(respBs))
}
var cr CommandResult
if err = json.Unmarshal(respBs, &cr); err != nil {
return CommandResult{}, err
}
return cr, nil
}
2025-02-27 05:15:49 -08:00
type authedHandler struct {
auth *authorization
mux *http.ServeMux
}
func (h authedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
2025-02-27 13:23:26 -08:00
err := h.auth.Refresh(r.Context())
2025-02-27 05:15:49 -08:00
if err != nil {
log.Printf("error authedHandler ServeHTTP refresh auth: %v", err)
http.Error(w, "", http.StatusInternalServerError)
return
}
a, err := addrFromServerAddress(r.RemoteAddr)
if err != nil {
log.Printf("error authedHandler ServeHTTP refresh auth: %v", err)
http.Error(w, "", http.StatusInternalServerError)
return
}
2025-02-27 13:23:26 -08:00
allowed := h.auth.AllowsHost(a)
2025-02-27 05:15:49 -08:00
if !allowed {
2025-02-27 07:39:16 -08:00
http.Error(w, "peer not allowed", http.StatusForbidden)
2025-02-27 05:15:49 -08:00
return
}
h.mux.ServeHTTP(w, r)
}
2025-02-27 05:15:49 -08:00
func (c *Consensus) makeCommandMux() *http.ServeMux {
mux := http.NewServeMux()
2025-02-27 05:15:49 -08:00
mux.HandleFunc("/join", func(w http.ResponseWriter, r *http.Request) {
2025-02-21 13:38:53 -08:00
if r.Method != httpm.POST {
2025-02-27 08:01:23 -08:00
http.Error(w, "Method must be POST", http.StatusMethodNotAllowed)
return
}
defer r.Body.Close()
2025-02-27 08:47:24 -08:00
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1024*1024))
var jr joinRequest
err := decoder.Decode(&jr)
if err != nil {
2025-02-27 08:47:24 -08:00
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
_, err = decoder.Token()
if !errors.Is(err, io.EOF) {
http.Error(w, "Request body must only contain a single JSON object", http.StatusBadRequest)
return
}
if jr.RemoteHost == "" {
http.Error(w, "Required: remoteAddr", http.StatusBadRequest)
return
}
if jr.RemoteID == "" {
http.Error(w, "Required: remoteID", http.StatusBadRequest)
return
}
err = c.handleJoin(jr)
if err != nil {
2025-02-27 08:47:24 -08:00
log.Printf("join handler error: %v", err)
http.Error(w, "", http.StatusInternalServerError)
return
}
2025-02-27 05:15:49 -08:00
})
mux.HandleFunc("/executeCommand", func(w http.ResponseWriter, r *http.Request) {
2025-02-21 13:38:53 -08:00
if r.Method != httpm.POST {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
defer r.Body.Close()
decoder := json.NewDecoder(r.Body)
var cmd Command
err := decoder.Decode(&cmd)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
result, err := c.executeCommandLocally(cmd)
2025-02-21 13:02:05 -08:00
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := json.NewEncoder(w).Encode(result); err != nil {
log.Printf("error encoding execute command result: %v", err)
return
}
2025-02-27 05:15:49 -08:00
})
return mux
}
2025-02-27 05:15:49 -08:00
func (c *Consensus) makeCommandHandler(auth *authorization) http.Handler {
return authedHandler{
mux: c.makeCommandMux(),
auth: auth,
}
}