Add permissions check, shasum check, & build info

Former-commit-id: 357f718e00
This commit is contained in:
Owen
2026-05-21 14:33:29 -07:00
parent c058500932
commit e1b6fa9008
3 changed files with 79 additions and 28 deletions

View File

@@ -43,31 +43,31 @@ go-build-release: \
go-build-release-freebsd-arm64
go-build-release-linux-arm64:
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o bin/newt_linux_arm64
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS) -X main.newtPlatform=linux_arm64" -o bin/newt_linux_arm64
go-build-release-linux-arm32-v7:
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags "$(LDFLAGS)" -o bin/newt_linux_arm32
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags "$(LDFLAGS) -X main.newtPlatform=linux_arm32" -o bin/newt_linux_arm32
go-build-release-linux-arm32-v6:
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags "$(LDFLAGS)" -o bin/newt_linux_arm32v6
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags "$(LDFLAGS) -X main.newtPlatform=linux_arm32v6" -o bin/newt_linux_arm32v6
go-build-release-linux-amd64:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o bin/newt_linux_amd64
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS) -X main.newtPlatform=linux_amd64" -o bin/newt_linux_amd64
go-build-release-linux-riscv64:
CGO_ENABLED=0 GOOS=linux GOARCH=riscv64 go build -ldflags "$(LDFLAGS)" -o bin/newt_linux_riscv64
CGO_ENABLED=0 GOOS=linux GOARCH=riscv64 go build -ldflags "$(LDFLAGS) -X main.newtPlatform=linux_riscv64" -o bin/newt_linux_riscv64
go-build-release-darwin-arm64:
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o bin/newt_darwin_arm64
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags "$(LDFLAGS) -X main.newtPlatform=darwin_arm64" -o bin/newt_darwin_arm64
go-build-release-darwin-amd64:
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o bin/newt_darwin_amd64
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS) -X main.newtPlatform=darwin_amd64" -o bin/newt_darwin_amd64
go-build-release-windows-amd64:
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o bin/newt_windows_amd64.exe
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS) -X main.newtPlatform=windows_amd64" -o bin/newt_windows_amd64.exe
go-build-release-freebsd-amd64:
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o bin/newt_freebsd_amd64
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS) -X main.newtPlatform=freebsd_amd64" -o bin/newt_freebsd_amd64
go-build-release-freebsd-arm64:
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o bin/newt_freebsd_arm64
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm64 go build -ldflags "$(LDFLAGS) -X main.newtPlatform=freebsd_arm64" -o bin/newt_freebsd_arm64

24
main.go
View File

@@ -147,18 +147,19 @@ var (
authDaemonEnabled bool
authDaemonGenerateRandomPassword bool
// Build/version (can be overridden via -ldflags "-X main.newtVersion=...")
newtVersion = "version_replaceme"
newtVersion = "version_replaceme"
newtPlatform = "" // embedded at build time via -X main.newtPlatform=<os>_<arch>
// Observability/metrics flags
metricsEnabled bool
otlpEnabled bool
adminAddr string
region string
metricsAsyncBytes bool
pprofEnabled bool
blueprintFile string
provisioningBlueprintFile string
noCloud bool
metricsEnabled bool
otlpEnabled bool
adminAddr string
region string
metricsAsyncBytes bool
pprofEnabled bool
blueprintFile string
provisioningBlueprintFile string
noCloud bool
// New mTLS configuration variables
tlsClientCert string
@@ -676,6 +677,7 @@ func runNewtMain(ctx context.Context) {
NewtID: id,
Secret: secret,
CurrentVersion: newtVersion,
Platform: newtPlatform,
TLSConfig: tlsCfg,
}); err != nil {
logger.Fatal("Auto-update failed: %v", err)
@@ -1867,7 +1869,7 @@ persistent_keepalive_interval=5`, util.FixKey(privateKey.String()), util.FixKey(
} else {
logger.Warn("CLIENTS WILL NOT WORK ON THIS VERSION OF NEWT WITH THIS VERSION OF PANGOLIN, PLEASE UPDATE THE SERVER TO 1.13 OR HIGHER OR DOWNGRADE NEWT")
}
sendBlueprint(client, blueprintFile)
if client.WasJustProvisioned() {
logger.Info("Provisioning detected sending provisioning blueprint")

View File

@@ -3,7 +3,9 @@ package updates
import (
"bytes"
"context"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"encoding/json"
"fmt"
"io"
@@ -26,6 +28,10 @@ type SelfUpdateConfig struct {
Secret string
// CurrentVersion is the version of the currently running binary.
CurrentVersion string
// Platform is the OS+arch string embedded at build time via ldflags
// (e.g. "linux_amd64", "darwin_arm64"). When non-empty it is used
// directly; when empty the value is derived from runtime.GOOS/GOARCH.
Platform string
// TLSConfig is an optional TLS configuration for the HTTP client (may be nil).
TLSConfig *tls.Config
}
@@ -33,9 +39,10 @@ type SelfUpdateConfig struct {
// versionResponse mirrors the JSON returned by POST /api/v1/auth/newt/version
type versionResponse struct {
Data struct {
LatestVersion string `json:"latestVersion"`
LatestVersion string `json:"latestVersion"`
CurrentIsLatest bool `json:"currentIsLatest"`
DownloadUrl string `json:"downloadUrl"`
DownloadUrl string `json:"downloadUrl"`
Sha256 string `json:"sha256"`
} `json:"data"`
Success bool `json:"success"`
Message string `json:"message"`
@@ -51,6 +58,7 @@ func isOfficialContainer() bool {
// platform returns the OS+arch string used in the newt release binary names,
// e.g. "linux_amd64", "darwin_arm64", "windows_amd64".
// It is used as a fallback when no platform was embedded at build time.
func platform() string {
goarch := runtime.GOARCH
goos := runtime.GOOS
@@ -71,6 +79,26 @@ func platform() string {
return fmt.Sprintf("%s_%s", goos, arch)
}
// verifySHA256 checks that the file at path has the expected SHA-256 hex digest.
func verifySHA256(path, expected string) error {
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("failed to open file for hashing: %w", err)
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return fmt.Errorf("failed to hash file: %w", err)
}
got := hex.EncodeToString(h.Sum(nil))
if !strings.EqualFold(got, expected) {
return fmt.Errorf("sha256 mismatch: expected %s, got %s", expected, got)
}
return nil
}
// CheckAndSelfUpdate contacts the pangolin server, checks whether a newer
// version of newt is available, downloads it if so, replaces the running
// binary on disk, and re-executes from the new binary.
@@ -107,10 +135,14 @@ func CheckAndSelfUpdate(cfg SelfUpdateConfig) error {
}
// --- Step 1: Ask the server for the latest version ---
plat := cfg.Platform
if plat == "" {
plat = platform()
}
reqBody, err := json.Marshal(map[string]string{
"newtId": cfg.NewtID,
"secret": cfg.Secret,
"platform": platform(),
"platform": plat,
})
if err != nil {
return fmt.Errorf("failed to marshal version request: %w", err)
@@ -159,6 +191,16 @@ func CheckAndSelfUpdate(cfg SelfUpdateConfig) error {
fmt.Printf("Update available: %s → %s\n", cfg.CurrentVersion, verResp.Data.LatestVersion)
fmt.Printf("Downloading from: %s\n", verResp.Data.DownloadUrl)
// --- Pre-download: verify we can write to the binary's directory ---
// Do this before downloading so a permission failure doesn't waste bandwidth.
exeDir := filepath.Dir(exePath)
writeTestFile, err := os.CreateTemp(exeDir, ".newt-write-test-*")
if err != nil {
return fmt.Errorf("cannot write to %s (you may need to run as root or with elevated permissions): %w", exeDir, err)
}
writeTestFile.Close()
_ = os.Remove(writeTestFile.Name())
// --- Step 2: Download the new binary ---
dlCtx, dlCancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer dlCancel()
@@ -180,11 +222,7 @@ func CheckAndSelfUpdate(cfg SelfUpdateConfig) error {
// Write to a temp file in the same directory as the current binary so that
// an atomic rename works even across filesystem boundaries.
exeDir := filepath.Dir(exePath)
tmpFile, err := os.CreateTemp(exeDir, ".newt-update-*")
if err != nil {
return fmt.Errorf("failed to create temp file for download: %w", err)
}
tmpPath := tmpFile.Name()
// Ensure the temp file is cleaned up on any error path.
@@ -200,6 +238,17 @@ func CheckAndSelfUpdate(cfg SelfUpdateConfig) error {
return fmt.Errorf("failed to close temp file: %w", err)
}
// --- Verify SHA256 checksum if the server provided one ---
if verResp.Data.Sha256 != "" {
fmt.Println("Verifying SHA256 checksum...")
if err := verifySHA256(tmpPath, verResp.Data.Sha256); err != nil {
return fmt.Errorf("binary integrity check failed: %w", err)
}
fmt.Println("SHA256 checksum verified.")
} else {
fmt.Println("Warning: no SHA256 checksum provided by server, skipping verification.")
}
// Make the new binary executable.
if err := os.Chmod(tmpPath, 0755); err != nil {
return fmt.Errorf("failed to set executable permission: %w", err)