feat: Removed comments
Server Deploy / deploy (push) Failing after 1m59s

This commit is contained in:
2026-07-24 09:51:30 +01:00
parent 3b52bcbeb8
commit 1a6cf03c03
94 changed files with 772 additions and 937 deletions
+11 -11
View File
@@ -13,9 +13,9 @@ import (
"github.com/wwt/guac"
)
// POST /api/console/connect
// Body: { server_id, protocol, key_id?, rdp_username?, rdp_password? }
// Returns: { session_id, token, ws_path }
func consoleConnect(c *gin.Context) {
var body struct {
ServerID string `json:"server_id" binding:"required"`
@@ -71,8 +71,8 @@ func consoleConnect(c *gin.Context) {
})
}
// queryIntDefault reads a positive integer query param, falling back to def
// when absent, unparseable, or non-positive.
func queryIntDefault(r *http.Request, key string, def int) int {
v, err := strconv.Atoi(r.URL.Query().Get(key))
if err != nil || v <= 0 {
@@ -81,7 +81,7 @@ func queryIntDefault(r *http.Request, key string, def int) int {
return v
}
// GET /api/console/tunnel?token=... (WebSocket upgrade)
func consoleTunnel(c *gin.Context) {
token := c.Query("token")
sessionID, err := services.VerifySessionToken(token)
@@ -96,14 +96,14 @@ func consoleTunnel(c *gin.Context) {
return
}
// User-bound: the caller (authenticated via session cookie) must be the same
// user who opened the session. Blocks a leaked token being used by someone else.
if actor := actorFromCtx(c); actor != sess.User {
c.JSON(http.StatusForbidden, gin.H{"error": "session belongs to another user"})
return
}
// Single-use: atomically spend the token so a replay within its TTL is rejected.
if err := services.ConsumeSessionToken(orgID, sessionID); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "token already used"})
return
@@ -115,7 +115,7 @@ func consoleTunnel(c *gin.Context) {
return
}
// Decrypt private key + passphrase in-memory only (ssh).
var privKey, passphrase string
if sess.Protocol == "ssh" && sess.KeyID != "" {
privKey, err = services.GetPrivateKey(auth.OrgID(c), sess.KeyID)
@@ -145,7 +145,7 @@ func consoleTunnel(c *gin.Context) {
guacdAddr = "guacd:4822"
}
// Build a guac tunnel config from our params.
connect := func(r *http.Request) (guac.Tunnel, error) {
config := guac.NewGuacamoleConfiguration()
config.Protocol = gp.Protocol
+5 -10
View File
@@ -25,14 +25,9 @@ func RegisterRoutes(r *gin.Engine) {
r.GET("/update", handleUpdateScript)
r.GET("/update.ps1", handleUpdateScriptWindows)
// ESO read endpoint — bearer-token auth, not session auth, so Kubernetes
// External Secrets Operator can call it. Lives under /api (so the reverse
// proxy routes it to the backend) but on a distinct subpath to avoid
// colliding with the session-authed GET /api/secrets/:group. Returns a
// group as flat JSON.
r.GET("/api/secrets/:group/values", secretsReadAuth(), esoGetGroup)
// Unauthenticated auth endpoints
r.GET("/auth/bootstrap-status", auth.HandleBootstrapStatus)
r.POST("/auth/bootstrap", auth.HandleBootstrap)
r.POST("/auth/login", auth.HandleLocalLogin)
@@ -41,7 +36,7 @@ func RegisterRoutes(r *gin.Engine) {
r.GET("/auth/oidc/start", auth.HandleOIDCStart)
r.GET("/auth/oidc/callback", auth.HandleOIDCCallback)
// API endpoints protected by session middleware
apiGroup := r.Group("/api")
apiGroup.Use(auth.Middleware())
{
@@ -168,7 +163,7 @@ func getServer(c *gin.Context) {
assignments, _ := services.GetAssignmentsWithKeysForServer(auth.OrgID(c), id)
// Build response matching ServerWithKeys shape expected by frontend
type serverResponse struct {
*models.Server
Keys interface{} `json:"keys"`
@@ -414,7 +409,7 @@ if [ -z "$LATEST" ]; then
fi
VERSION="${LATEST#agent/}"
LATEST_ENCODED="${LATEST/\//%%2F}"
LATEST_ENCODED="${LATEST/\
BINARY_URL="https://${GITEA_HOST}/mrhid6/vantage/releases/download/${LATEST_ENCODED}/vantage-agent-linux-${ARCH}"
CHECKSUM_URL="https://${GITEA_HOST}/mrhid6/vantage/releases/download/${LATEST_ENCODED}/checksums.txt"
@@ -521,7 +516,7 @@ if [ -z "$LATEST" ]; then
fi
VERSION="${LATEST#agent/}"
LATEST_ENCODED="${LATEST/\//%%2F}"
LATEST_ENCODED="${LATEST/\
BINARY_URL="https://${GITEA_HOST}/mrhid6/vantage/releases/download/${LATEST_ENCODED}/vantage-agent-linux-${ARCH}"
CHECKSUM_URL="https://${GITEA_HOST}/mrhid6/vantage/releases/download/${LATEST_ENCODED}/checksums.txt"
+4 -4
View File
@@ -16,7 +16,7 @@ func handleInstallScriptWindows(c *gin.Context) {
if giteaHost == "" {
giteaHost = "gitea.example.com"
}
// Guaranteed non-empty: main() fatals at boot if GRPC_HOST is unset.
grpcHost := os.Getenv("GRPC_HOST")
script := fmt.Sprintf(
@@ -50,9 +50,9 @@ func handleInstallScriptWindows(c *gin.Context) {
c.String(http.StatusOK, script)
}
// handleUpdateScriptWindows serves a PowerShell one-liner that upgrades an
// already-installed Windows agent. No server_id/token needed: the MSI is a
// MajorUpgrade and setup.ps1 preserves the existing config on upgrade.
func handleUpdateScriptWindows(c *gin.Context) {
giteaHost := os.Getenv("GITEA_HOST")
if giteaHost == "" {
+6 -9
View File
@@ -19,9 +19,9 @@ func listOrgUsers(c *gin.Context) {
c.JSON(http.StatusOK, users)
}
// Granting or removing the owner role is reserved to owners: an admin must
// never be able to mint an owner (and log in as it) or strip the owners above
// them. Everything below derives the actor from the session, never the body.
func actorMayGrantOwner(c *gin.Context) bool {
return auth.Role(c) == models.RoleOwner
}
@@ -113,8 +113,6 @@ func deleteOrgUser(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"deleted": true})
}
// The last-owner guard is a rejected request, not a server fault — surface it
// as 409 so the UI shows the message rather than a generic failure.
func orgUserErrStatus(err error) int {
if errors.Is(err, services.ErrLastOwner) {
return http.StatusConflict
@@ -128,8 +126,8 @@ func getOrgOIDC(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"enabled": false, "client_secret_set": false})
return
}
// The client secret itself is write-only (never serialized); expose only
// whether one is stored so the UI can say so without leaking it.
c.JSON(http.StatusOK, gin.H{
"org_id": cfg.OrgID,
"issuer": cfg.Issuer,
@@ -155,8 +153,7 @@ func putOrgOIDC(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Drop the cached provider so a rotated issuer takes effect immediately —
// an admin moving off a compromised IdP must not keep authenticating there.
auth.EvictOIDCProvider(auth.OrgID(c))
c.JSON(http.StatusOK, gin.H{"saved": true})
}
+17 -17
View File
@@ -11,23 +11,23 @@ import (
"github.com/mrhid6/vantage/server/internal/services"
)
// groupNamePattern restricts group and key names to characters that are safe
// in URLs and Kubernetes/env contexts.
var groupNamePattern = regexp.MustCompile(`^[A-Za-z0-9._-]+$`)
func validName(s string) bool {
return s != "" && len(s) <= 128 && groupNamePattern.MatchString(s)
}
// ctxSecretsOrgKey carries the org resolved from the ESO bearer token.
const ctxSecretsOrgKey = "km_secrets_org"
// secretsReadAuth validates the ESO bearer token on the public read endpoint
// and stashes the org the token belongs to.
//
// This is the one endpoint whose org does NOT come from the session or the
// host: External Secrets Operator calls it machine-to-machine with no session,
// so the token itself is the org-bearing credential.
func secretsReadAuth() gin.HandlerFunc {
return func(c *gin.Context) {
const prefix = "Bearer "
@@ -46,15 +46,15 @@ func secretsReadAuth() gin.HandlerFunc {
}
}
// esoGetGroup handles GET /secrets/:group for the External Secrets Operator.
// Returns a flat JSON object { "KEY": "value", ... }; 404 if the group is empty
// (ESO treats 404 as "deleted").
func esoGetGroup(c *gin.Context) {
group := c.Param("group")
// Org comes from the bearer token (set by secretsReadAuth), not a session.
orgID := c.GetString(ctxSecretsOrgKey)
if orgID == "" {
// Defence in depth: never query the store unscoped.
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
return
}
@@ -79,8 +79,8 @@ func listSecretGroups(c *gin.Context) {
c.JSON(http.StatusOK, groups)
}
// createSecretGroup handles POST /api/secrets. A group is implicit, so it must
// be created with at least one key/value pair.
func createSecretGroup(c *gin.Context) {
var body struct {
Group string `json:"group" binding:"required"`
@@ -126,7 +126,7 @@ func getSecretGroup(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"group": group, "secrets": secrets})
}
// putSecretGroup upserts one or more keys into an existing (or new) group.
func putSecretGroup(c *gin.Context) {
group := c.Param("group")
if !validName(group) {
+7 -9
View File
@@ -81,7 +81,7 @@ func streamServerRunLog(c *gin.Context) {
sendNew := func() {
f, err := os.Open(path)
if err != nil {
return // file may not exist yet; keep waiting
return
}
defer f.Close()
if _, err := f.Seek(offset, 0); err != nil {
@@ -94,7 +94,7 @@ func streamServerRunLog(c *gin.Context) {
break
}
offset += int64(n)
// SSE data frame; split on newlines to keep frames well-formed.
for _, line := range splitSSE(buf[:n]) {
_, _ = c.Writer.WriteString("data: " + line + "\n")
}
@@ -110,7 +110,7 @@ func streamServerRunLog(c *gin.Context) {
for {
sendNew()
if serverRunTerminal(orgID, runID, serverID) {
sendNew() // final drain
sendNew()
_, _ = c.Writer.WriteString("event: done\ndata: end\n\n")
flusher.Flush()
return
@@ -123,7 +123,7 @@ func streamServerRunLog(c *gin.Context) {
}
}
// serverRunTerminal reports whether the given server-run has reached a terminal status.
func serverRunTerminal(orgID, runID, serverID string) bool {
r, err := services.GetRun(orgID, runID)
if err != nil {
@@ -141,8 +141,8 @@ func serverRunTerminal(orgID, runID, serverID string) bool {
return true
}
// splitSSE turns a raw byte slice into SSE-safe payload lines (newlines become
// separate data lines; carriage returns stripped).
func splitSSE(b []byte) []string {
s := strings.ReplaceAll(string(b), "\r", "")
return strings.Split(s, "\n")
@@ -224,7 +224,7 @@ func seedDefaults(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"created": created, "updated": updated})
}
const maxStepBodyBytes = 1 << 20 // 1 MiB
const maxStepBodyBytes = 1 << 20
func importStep(c *gin.Context) {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxStepBodyBytes)
@@ -242,8 +242,6 @@ func importStep(c *gin.Context) {
c.JSON(http.StatusCreated, out)
}
// parseStep validates a step doc and returns the normalized step WITHOUT
// persisting — used by the editor to insert an imported ad-hoc (inline) step.
func parseStep(c *gin.Context) {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxStepBodyBytes)
body, err := io.ReadAll(c.Request.Body)