This commit is contained in:
2026-07-25 18:42:09 -05:00
parent 7a02c50aa9
commit ec57321577
11 changed files with 136 additions and 28 deletions
+33 -12
View File
@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strings"
@@ -16,6 +17,7 @@ type exchangeRequest struct {
Code string `json:"code"`
HostFingerprint string `json:"host_fingerprint"`
Hostname string `json:"hostname"`
LANIP string `json:"lan_ip,omitempty"`
RequestedAction string `json:"requested_action,omitempty"`
RequestedPackage string `json:"requested_package,omitempty"`
}
@@ -57,8 +59,17 @@ func (s *Server) handleExchange(w http.ResponseWriter, r *http.Request) {
request.Code = normalizeCode(request.Code)
request.HostFingerprint = strings.TrimSpace(request.HostFingerprint)
request.Hostname = strings.TrimSpace(request.Hostname)
request.LANIP = strings.TrimSpace(request.LANIP)
request.RequestedAction = strings.TrimSpace(request.RequestedAction)
request.RequestedPackage = strings.TrimSpace(request.RequestedPackage)
if request.LANIP != "" {
parsedLANIP := net.ParseIP(request.LANIP)
if parsedLANIP == nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "lan_ip must be a valid IP address"})
return
}
request.LANIP = parsedLANIP.String()
}
if request.Code == "" || len(request.HostFingerprint) < 16 ||
request.Hostname == "" || len(request.Hostname) > 255 ||
(request.RequestedAction != "" && !validSlug(request.RequestedAction)) ||
@@ -75,11 +86,12 @@ func (s *Server) handleExchange(w http.ResponseWriter, r *http.Request) {
status = http.StatusForbidden
message = "authorization is invalid, expired, revoked, or at its host limit"
}
_ = s.audit(r.Context(), "code_exchange_failed", "", nil, request.Hostname, "", sourceIP, err.Error())
_ = s.auditWithNetwork(r.Context(), "code_exchange_failed", "", nil,
request.Hostname, "", sourceIP, request.LANIP, err.Error())
writeJSON(w, status, map[string]string{"error": message})
return
}
_ = s.audit(
_ = s.auditWithNetwork(
r.Context(),
"code_exchanged",
"",
@@ -87,6 +99,7 @@ func (s *Server) handleExchange(w http.ResponseWriter, r *http.Request) {
request.Hostname,
request.RequestedPackage,
sourceIP,
request.LANIP,
"requested_action="+request.RequestedAction,
)
writeJSON(w, http.StatusOK, response)
@@ -199,9 +212,9 @@ func (s *Server) exchangeDeploymentCode(
result, err := tx.ExecContext(
r.Context(),
`INSERT INTO authorization_hosts
(authorization_id, host_fingerprint, hostname)
VALUES (?, ?, ?)`,
authorizationID, fingerprintHash[:], request.Hostname,
(authorization_id, host_fingerprint, hostname, lan_ip)
VALUES (?, ?, ?, ?)`,
authorizationID, fingerprintHash[:], request.Hostname, request.LANIP,
)
if err != nil {
return response, 0, err
@@ -214,9 +227,11 @@ func (s *Server) exchangeDeploymentCode(
_, err = tx.ExecContext(
r.Context(),
`UPDATE authorization_hosts
SET hostname = ?, last_seen_at = UTC_TIMESTAMP(6)
SET hostname = ?,
lan_ip = COALESCE(NULLIF(?, ''), lan_ip),
last_seen_at = UTC_TIMESTAMP(6)
WHERE id = ?`,
request.Hostname, hostID,
request.Hostname, request.LANIP, hostID,
)
if err != nil {
return response, 0, err
@@ -328,11 +343,12 @@ func (s *Server) handlePackageDownload(w http.ResponseWriter, r *http.Request) {
var packageInfo packageRecord
var authorizationID uint64
var hostname string
var lanIP string
err := s.db.QueryRowContext(
r.Context(),
`SELECT p.id, p.slug, p.display_name, p.package_name,
p.package_version, p.file_name, p.sha256, p.enabled,
ds.authorization_id, ah.hostname
ds.authorization_id, ah.hostname, ah.lan_ip
FROM download_sessions ds
JOIN authorizations a ON a.id = ds.authorization_id
JOIN authorization_hosts ah ON ah.id = ds.authorization_host_id
@@ -357,6 +373,7 @@ func (s *Server) handlePackageDownload(w http.ResponseWriter, r *http.Request) {
&packageInfo.Enabled,
&authorizationID,
&hostname,
&lanIP,
)
if errors.Is(err, sql.ErrNoRows) {
writeJSON(w, http.StatusForbidden, map[string]string{"error": "package is not authorized"})
@@ -385,13 +402,15 @@ func (s *Server) handlePackageDownload(w http.ResponseWriter, r *http.Request) {
upstreamRequest.SetBasicAuth(s.cfg.GiteaPackageUser, s.cfg.GiteaPackageToken)
upstreamResponse, err := s.packageClient.Do(upstreamRequest)
if err != nil {
_ = s.audit(r.Context(), "package_download_failed", "", &authorizationID, hostname, slug, s.clientIP(r), err.Error())
_ = s.auditWithNetwork(r.Context(), "package_download_failed", "",
&authorizationID, hostname, slug, s.clientIP(r), lanIP, err.Error())
writeJSON(w, http.StatusBadGateway, map[string]string{"error": "package registry is unavailable"})
return
}
defer upstreamResponse.Body.Close()
if upstreamResponse.StatusCode != http.StatusOK {
_ = s.audit(r.Context(), "package_download_failed", "", &authorizationID, hostname, slug, s.clientIP(r), upstreamResponse.Status)
_ = s.auditWithNetwork(r.Context(), "package_download_failed", "",
&authorizationID, hostname, slug, s.clientIP(r), lanIP, upstreamResponse.Status)
writeJSON(w, http.StatusBadGateway, map[string]string{"error": "package registry rejected the request"})
return
}
@@ -402,8 +421,10 @@ func (s *Server) handlePackageDownload(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-store")
w.WriteHeader(http.StatusOK)
if _, err := io.Copy(w, upstreamResponse.Body); err != nil {
_ = s.audit(r.Context(), "package_download_failed", "", &authorizationID, hostname, slug, s.clientIP(r), err.Error())
_ = s.auditWithNetwork(r.Context(), "package_download_failed", "",
&authorizationID, hostname, slug, s.clientIP(r), lanIP, err.Error())
return
}
_ = s.audit(r.Context(), "package_downloaded", "", &authorizationID, hostname, slug, s.clientIP(r), "")
_ = s.auditWithNetwork(r.Context(), "package_downloaded", "",
&authorizationID, hostname, slug, s.clientIP(r), lanIP, "")
}