Harden image admission, resource recovery and session renewal
Add exact-image compatibility policy, experimental admission, paginated build history and shared reauthentication checks. Preserve uncertain Docker creation records, bound frontend resource waits and renew runtime sessions proactively. Lazy-load and split Monaco, add regression and opt-in live Docker fault tests, and record full race and real-browser acceptance results.
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Admission writes serialize with new runtime operations, including their Docker side effects.
|
||||
var admissionMu sync.RWMutex
|
||||
var errBuildUnsupported = errors.New("version is not supported for normal builds; administrator experimental build required")
|
||||
|
||||
func defaultAdmission(version, recipe, platform string) string {
|
||||
if recipe == "7" && platform == "linux/amd64" {
|
||||
switch version {
|
||||
case "0.2.0-rc.1":
|
||||
return "compatible"
|
||||
case "0.2.0-rc.2":
|
||||
return "recommended"
|
||||
}
|
||||
}
|
||||
return "existing_only"
|
||||
}
|
||||
func supportedLayout(recipe string) bool {
|
||||
switch recipe {
|
||||
case "3", "4", "5", "6", "7":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
func normalBuildAllowed(version string) bool {
|
||||
return positiveAdmission(defaultAdmission(version, imageRecipeRev, imagePlatform))
|
||||
}
|
||||
func positiveAdmission(state string) bool { return state == "recommended" || state == "compatible" }
|
||||
func (a *API) compatibility(w http.ResponseWriter, r *http.Request) {
|
||||
if _, _, err := a.user(r); err != nil {
|
||||
fail(w, 401, "authentication required")
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodGet {
|
||||
fail(w, 405, "method not allowed")
|
||||
return
|
||||
}
|
||||
entries := []map[string]any{}
|
||||
for _, v := range []string{"0.2.0-rc.1", "0.2.0-rc.2"} {
|
||||
entries = append(entries, map[string]any{"version": v, "recipe_rev": imageRecipeRev, "platform": imagePlatform, "state": defaultAdmission(v, imageRecipeRev, imagePlatform), "build_allowed": normalBuildAllowed(v), "reason": "only linux/amd64 recipe 7 is built-in; successful exact image verification still required"})
|
||||
}
|
||||
writeJSON(w, map[string]any{"recipe_rev": imageRecipeRev, "platform": imagePlatform, "entries": entries, "unknown": map[string]any{"state": "existing_only", "build_allowed": false, "reason": "not accepted; administrator experimental build and explicit admission required"}, "experimental_admin_only": true})
|
||||
}
|
||||
|
||||
func (a *API) evaluateImage(ctx context.Context, id string) (imageRecord, error) {
|
||||
im := imageRecord{ID: id, ImageID: id, Status: "missing", Admission: "existing_only", AdmissionReason: "local image missing"}
|
||||
actual, err := a.inspectLocalImage(ctx, id)
|
||||
if err != nil || actual == nil {
|
||||
return im, err
|
||||
}
|
||||
im.Version = actual.Config.Labels["dsh-mgr.version"]
|
||||
im.RecipeRev = actual.Config.Labels["dsh-mgr.recipe"]
|
||||
im.Platform = actual.platform()
|
||||
im.Created = actual.Created
|
||||
im.Status = "unverified"
|
||||
im.AdmissionReason = "successful exact image identity verification required"
|
||||
if actual.ID != id || actual.Config.Labels["dsh-mgr.role"] != "runtime" {
|
||||
im.Version = ""
|
||||
return im, nil
|
||||
}
|
||||
var count, experimental int
|
||||
err = a.s.DB.QueryRowContext(ctx, "SELECT count(*),COALESCE(min(experimental),0) FROM image_builds WHERE image_id=? AND version=? AND recipe_rev=? AND platform=? AND status='success'", id, im.Version, im.RecipeRev, im.Platform).Scan(&count, &experimental)
|
||||
if err != nil {
|
||||
return im, err
|
||||
}
|
||||
var state, evidence string
|
||||
err = a.s.DB.QueryRowContext(ctx, "SELECT state,evidence FROM image_admissions WHERE image_id=? AND version=? AND recipe_rev=? AND platform=?", id, im.Version, im.RecipeRev, im.Platform).Scan(&state, &evidence)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return im, err
|
||||
}
|
||||
if state == "blocked" {
|
||||
im.Admission = state
|
||||
im.Status = state
|
||||
im.AdmissionReason = evidence
|
||||
return im, nil
|
||||
}
|
||||
if count == 0 {
|
||||
return im, nil
|
||||
}
|
||||
im.Admission = defaultAdmission(im.Version, im.RecipeRev, im.Platform)
|
||||
im.AdmissionReason = "no accepted compatibility evidence for this tuple"
|
||||
if positiveAdmission(im.Admission) {
|
||||
im.AdmissionReason = "built-in exact tuple compatibility"
|
||||
}
|
||||
if experimental != 0 {
|
||||
im.Admission = "existing_only"
|
||||
im.Status = "experimental"
|
||||
im.AdmissionReason = "experimental validation succeeded; administrator acceptance required"
|
||||
} else {
|
||||
im.Status = "existing_only"
|
||||
}
|
||||
if state != "" {
|
||||
im.Admission = state
|
||||
im.AdmissionReason = evidence
|
||||
im.Status = state
|
||||
}
|
||||
if !supportedLayout(im.RecipeRev) || im.Platform != imagePlatform {
|
||||
im.Admission = "existing_only"
|
||||
im.Status = "existing_only"
|
||||
im.AdmissionReason = "historical validation retained; current mount layout or platform not supported"
|
||||
}
|
||||
im.Selectable = positiveAdmission(im.Admission)
|
||||
if im.Selectable {
|
||||
im.Status = "available"
|
||||
}
|
||||
return im, nil
|
||||
}
|
||||
|
||||
func (a *API) imageAdmission(w http.ResponseWriter, r *http.Request) {
|
||||
u, _, err := a.user(r)
|
||||
if err != nil {
|
||||
fail(w, 401, "authentication required")
|
||||
return
|
||||
}
|
||||
if !u.Admin {
|
||||
fail(w, 403, "administrator required")
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodPost {
|
||||
fail(w, 405, "method not allowed")
|
||||
return
|
||||
}
|
||||
var in struct {
|
||||
ImageID string `json:"image_id"`
|
||||
State string `json:"state"`
|
||||
Evidence string `json:"evidence"`
|
||||
}
|
||||
dec := json.NewDecoder(http.MaxBytesReader(w, r.Body, 8192))
|
||||
dec.DisallowUnknownFields()
|
||||
if dec.Decode(&in) != nil || dec.Decode(new(any)) != io.EOF || !runtimeImageID.MatchString(in.ImageID) || strings.TrimSpace(in.Evidence) == "" || (!positiveAdmission(in.State) && in.State != "existing_only" && in.State != "blocked") {
|
||||
fail(w, 400, "image_id, valid state and evidence required")
|
||||
return
|
||||
}
|
||||
if len(in.Evidence) > 4096 {
|
||||
fail(w, 400, "evidence must not exceed 4096 UTF-8 bytes")
|
||||
return
|
||||
}
|
||||
admissionMu.Lock()
|
||||
defer admissionMu.Unlock()
|
||||
buildQueue.Lock()
|
||||
err = a.ensureBuilds()
|
||||
buildQueue.Unlock()
|
||||
if err != nil {
|
||||
fail(w, 500, "database error")
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
|
||||
defer cancel()
|
||||
im, err := a.evaluateImage(ctx, in.ImageID)
|
||||
if err != nil {
|
||||
fail(w, 502, "image inspection failed")
|
||||
return
|
||||
}
|
||||
if im.Status == "missing" || im.Version == "" || im.RecipeRev == "" || im.Platform == "" {
|
||||
fail(w, 400, "real runtime image identity required")
|
||||
return
|
||||
}
|
||||
if positiveAdmission(in.State) {
|
||||
var count int
|
||||
err = a.s.DB.QueryRowContext(ctx, "SELECT count(*) FROM image_builds WHERE image_id=? AND version=? AND recipe_rev=? AND platform=? AND status='success'", im.ImageID, im.Version, im.RecipeRev, im.Platform).Scan(&count)
|
||||
if err != nil || count == 0 || !supportedLayout(im.RecipeRev) || im.Platform != imagePlatform {
|
||||
fail(w, 400, "successful exact validation and supported mount layout/platform required")
|
||||
return
|
||||
}
|
||||
}
|
||||
buildQueue.Lock()
|
||||
defer buildQueue.Unlock()
|
||||
tx, err := a.s.DB.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
fail(w, 500, "database error")
|
||||
return
|
||||
}
|
||||
defer tx.Rollback()
|
||||
_, err = tx.ExecContext(ctx, "INSERT INTO image_admissions(image_id,version,recipe_rev,platform,state,evidence,admitted_by,admitted_at) VALUES(?,?,?,?,?,?,?,?) ON CONFLICT(image_id,version,recipe_rev,platform) DO UPDATE SET state=excluded.state,evidence=excluded.evidence,admitted_by=excluded.admitted_by,admitted_at=excluded.admitted_at", im.ImageID, im.Version, im.RecipeRev, im.Platform, in.State, strings.TrimSpace(in.Evidence), u.ID, time.Now().UTC().Format(time.RFC3339Nano))
|
||||
if err != nil {
|
||||
fail(w, 500, "database error")
|
||||
return
|
||||
}
|
||||
if _, err = tx.ExecContext(ctx, "INSERT INTO audit(uid,action,target,detail,at) VALUES(?,?,?,?,?)", u.ID, "image.admission", im.ImageID, in.State+": "+in.Evidence, time.Now().Unix()); err != nil {
|
||||
fail(w, 500, "audit persistence failed")
|
||||
return
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
fail(w, 500, "database error")
|
||||
return
|
||||
}
|
||||
a.imageDeleteGeneration++
|
||||
im.Admission = in.State
|
||||
im.AdmissionReason = strings.TrimSpace(in.Evidence)
|
||||
im.Selectable = positiveAdmission(in.State)
|
||||
im.Status = in.State
|
||||
if im.Selectable {
|
||||
im.Status = "available"
|
||||
}
|
||||
writeJSON(w, im)
|
||||
}
|
||||
Reference in New Issue
Block a user