1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
|
package main
import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net/http"
"github.com/gorilla/mux"
internalIO "github.com/lxc/incus/v6/internal/io"
"github.com/lxc/incus/v6/internal/server/response"
localUtil "github.com/lxc/incus/v6/internal/server/util"
"github.com/lxc/incus/v6/shared/logger"
)
func restServer(tlsConfig *tls.Config, cert *x509.Certificate, debug bool, d *Daemon) *http.Server {
mux := mux.NewRouter()
mux.StrictSlash(false) // Don't redirect to URL with trailing slash.
mux.UseEncodedPath() // Allow encoded values in path segments.
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = response.SyncResponse(true, []string{"/1.0"}).Render(w)
})
for _, c := range api10 {
createCmd(mux, "1.0", c, cert, debug, d)
}
return &http.Server{Handler: mux, TLSConfig: tlsConfig}
}
func createCmd(restAPI *mux.Router, version string, c APIEndpoint, cert *x509.Certificate, debug bool, d *Daemon) {
var uri string
if c.Path == "" {
uri = fmt.Sprintf("/%s", version)
} else {
uri = fmt.Sprintf("/%s/%s", version, c.Path)
}
route := restAPI.HandleFunc(uri, func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if !authenticate(r, cert) {
logger.Error("Not authorized")
_ = response.InternalError(fmt.Errorf("Not authorized")).Render(w)
return
}
// Dump full request JSON when in debug mode
if r.Method != "GET" && localUtil.IsJSONRequest(r) {
newBody := &bytes.Buffer{}
captured := &bytes.Buffer{}
multiW := io.MultiWriter(newBody, captured)
_, err := io.Copy(multiW, r.Body)
if err != nil {
_ = response.InternalError(err).Render(w)
return
}
r.Body = internalIO.BytesReadCloser{Buf: newBody}
localUtil.DebugJSON("API Request", captured, logger.Log)
}
// Actually process the request
var resp response.Response
handleRequest := func(action APIEndpointAction) response.Response {
if action.Handler == nil {
return response.NotImplemented(nil)
}
return action.Handler(d, r)
}
switch r.Method {
case "GET":
resp = handleRequest(c.Get)
case "PUT":
resp = handleRequest(c.Put)
case "POST":
resp = handleRequest(c.Post)
case "DELETE":
resp = handleRequest(c.Delete)
case "PATCH":
resp = handleRequest(c.Patch)
default:
resp = response.NotFound(fmt.Errorf("Method %q not found", r.Method))
}
// Handle errors
err := resp.Render(w)
if err != nil {
writeErr := response.InternalError(err).Render(w)
if writeErr != nil {
logger.Error("Failed writing error for HTTP response", logger.Ctx{"url": uri, "error": err, "writeErr": writeErr})
}
}
})
// If the endpoint has a canonical name then record it so it can be used to build URLS
// and accessed in the context of the request by the handler function.
if c.Name != "" {
route.Name(c.Name)
}
}
func authenticate(r *http.Request, cert *x509.Certificate) bool {
clientCerts := map[string]x509.Certificate{"0": *cert}
for _, cert := range r.TLS.PeerCertificates {
trusted, _ := localUtil.CheckTrustState(*cert, clientCerts, nil, false)
if trusted {
return true
}
}
return false
}
|