hub v0.29.0: Day-0 artifact manifest — version dropdowns + auto-derived sha
Operator picks a version from a Gitea-populated dropdown; the hub reads that version's sha256 from Gitea itself (files-metadata API, no artifact download) and vouches it — no hand-copied checksums. New internal/gitea read-only client (ListVersions + FileSHA256, unit-tested). Configuration UI: version <select>s + read-only sha display; handleSetArtifacts derives the sha authoritatively and refuses the save on a Gitea lookup failure. Degrades to manual text entry without registry creds. go build/vet/test clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
@@ -635,22 +636,24 @@ func normalizeSHA256(raw string) (string, bool) {
|
||||
}
|
||||
|
||||
// handleSetArtifacts records the operator-vouched current artifact set (agent binary + golden
|
||||
// archive: version + sha256 each) into hub_settings. This is the checksum TRUST ROOT the
|
||||
// host-bootstrap script verifies fetched artifacts against. Versions are validated as bare semver
|
||||
// (reusing the floor validator); sha256s as 64-hex. Empty fields are allowed (clears that field).
|
||||
// archive) into hub_settings — the checksum TRUST ROOT the host-bootstrap script verifies fetched
|
||||
// artifacts against. The operator picks a VERSION (from the Gitea-populated dropdown); the hub DERIVES
|
||||
// that version's sha256 from Gitea itself (never trusting a client-supplied checksum), so there is no
|
||||
// hand-copied sha to get wrong. When no Gitea client is configured (no registry creds) it falls back
|
||||
// to the submitted sha256 (legacy manual path). Empty version clears that artifact.
|
||||
func (s *Server) handleSetArtifacts(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "Bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
agentVer, okAV := normalizeFloorInput(r.FormValue("agent_version"))
|
||||
agentSHA, okAS := normalizeSHA256(r.FormValue("agent_sha256"))
|
||||
goldenVer, okGV := normalizeFloorInput(r.FormValue("golden_version"))
|
||||
goldenSHA, okGS := normalizeSHA256(r.FormValue("golden_sha256"))
|
||||
if !okAV || !okGV {
|
||||
http.Redirect(w, r, "/configuration?flash=artifact_ver_invalid", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
agentSHA, okAS := s.resolveArtifactSHA(r.Context(), pkgAgent, fileAgent, agentVer, r.FormValue("agent_sha256"))
|
||||
goldenSHA, okGS := s.resolveArtifactSHA(r.Context(), pkgGolden, fileGolden, goldenVer, r.FormValue("golden_sha256"))
|
||||
if !okAS || !okGS {
|
||||
http.Redirect(w, r, "/configuration?flash=artifact_sha_invalid", http.StatusSeeOther)
|
||||
return
|
||||
@@ -669,6 +672,26 @@ func (s *Server) handleSetArtifacts(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/configuration?flash=artifacts_set", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// resolveArtifactSHA determines the sha256 to store for a chosen artifact version. An empty version
|
||||
// clears the artifact (returns "",true). With a Gitea client it fetches the sha AUTHORITATIVELY from
|
||||
// Gitea (the submitted value is ignored — nothing hand-typed to trust); a fetch failure returns
|
||||
// (_,false) so the caller refuses the save rather than storing a version with a wrong/blank checksum.
|
||||
// Without a Gitea client it validates + uses the submitted sha (legacy manual path).
|
||||
func (s *Server) resolveArtifactSHA(ctx context.Context, pkg, file, version, submittedSHA string) (string, bool) {
|
||||
if version == "" {
|
||||
return "", true
|
||||
}
|
||||
if s.gitea != nil {
|
||||
sha, err := s.gitea.FileSHA256(ctx, pkg, version, file)
|
||||
if err != nil {
|
||||
s.logger.Printf("[WARN] artifact sha resolve (%s/%s): %v", pkg, version, err)
|
||||
return "", false
|
||||
}
|
||||
return sha, true
|
||||
}
|
||||
return normalizeSHA256(submittedSHA)
|
||||
}
|
||||
|
||||
// handleSetCustomerFloor sets (or clears) a customer's per-customer controller-version floor
|
||||
// override. Empty clears the override (the customer then uses the global floor).
|
||||
func (s *Server) handleSetCustomerFloor(w http.ResponseWriter, r *http.Request, customerID string) {
|
||||
|
||||
Reference in New Issue
Block a user