Files
olons_server/internal/httpapi/internal_auth.go
T

73 lines
2.0 KiB
Go

package httpapi
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
"strconv"
"strings"
"time"
)
const internalSignatureWindow = 5 * time.Minute
func (a *API) withInternalAdminAuth(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(io.LimitReader(r.Body, 10<<20))
if err != nil {
writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", nil)
return
}
_ = r.Body.Close()
r.Body = io.NopCloser(bytes.NewReader(body))
if err := a.verifyInternalAdminSignature(r, body); err != nil {
writeError(w, r, http.StatusForbidden, 30041, reasonForbidden, "forbidden", nil)
return
}
next(w, r)
}
}
func (a *API) verifyInternalAdminSignature(r *http.Request, body []byte) error {
secret := strings.TrimSpace(a.authCfg.InternalSecret)
if secret == "" {
return ErrForbidden
}
serviceName := strings.TrimSpace(r.Header.Get("X-Service-Name"))
if expected := strings.TrimSpace(a.authCfg.InternalServiceName); expected != "" && serviceName != expected {
return ErrForbidden
}
timestampRaw := strings.TrimSpace(r.Header.Get("X-Timestamp"))
nonce := strings.TrimSpace(r.Header.Get("X-Nonce"))
signature := strings.TrimSpace(r.Header.Get("X-Signature"))
if timestampRaw == "" || nonce == "" || signature == "" {
return ErrForbidden
}
ts, err := strconv.ParseInt(timestampRaw, 10, 64)
if err != nil {
return ErrForbidden
}
when := time.Unix(ts, 0)
if time.Since(when) > internalSignatureWindow || time.Until(when) > internalSignatureWindow {
return ErrForbidden
}
bodyHash := sha256.Sum256(body)
signingText := strings.Join([]string{
r.Method,
r.URL.RequestURI(),
hex.EncodeToString(bodyHash[:]),
timestampRaw,
nonce,
}, "\n")
mac := hmac.New(sha256.New, []byte(secret))
_, _ = mac.Write([]byte(signingText))
expected := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expected), []byte(signature)) {
return ErrForbidden
}
return nil
}