trusted.pub now holds the release signing key, so self-upgrade, agent upgrade and node onboarding refuse a release whose checksums are not signed with it. Onboarding fetched the release on the node and compared it with a checksum from the same server; the control plane now fetches it for the node's architecture with release.Fetch, checks signature and checksum, and hands the bytes over. Release addresses must be https (loopback excepted). LLMGW_RELEASE_KEY overrides the key only in development builds. The CI release job fails instead of publishing an unsigned release while a key is built in. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
107 lines
3.5 KiB
Go
107 lines
3.5 KiB
Go
package release
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func releaseServer(t *testing.T, version, asset string, body []byte, sum string) *httptest.Server {
|
|
t.Helper()
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Path {
|
|
case "/" + version + "/checksums.txt":
|
|
_, _ = w.Write([]byte(sum + " " + asset + "\n"))
|
|
case "/" + version + "/" + asset:
|
|
_, _ = w.Write(body)
|
|
default:
|
|
http.NotFound(w, r)
|
|
}
|
|
}))
|
|
t.Cleanup(srv.Close)
|
|
return srv
|
|
}
|
|
|
|
// unsigned has this build trust no key for the test: the checksum alone.
|
|
func unsigned(t *testing.T) {
|
|
t.Helper()
|
|
was := trustedPub
|
|
trustedPub = ""
|
|
t.Setenv("LLMGW_RELEASE_KEY", "")
|
|
t.Cleanup(func() { trustedPub = was })
|
|
}
|
|
|
|
func TestABinaryIsReplacedOnlyWithWhatTheReleasePublished(t *testing.T) {
|
|
unsigned(t)
|
|
dir := t.TempDir()
|
|
target := filepath.Join(dir, "llmgw")
|
|
_ = os.WriteFile(target, []byte("old"), 0o755)
|
|
body := []byte("new binary")
|
|
sum := sha256.Sum256(body)
|
|
asset := AssetName("v1.2.3", "linux", "amd64")
|
|
|
|
srv := releaseServer(t, "v1.2.3", asset, body, hex.EncodeToString(sum[:]))
|
|
backup, err := Install(t.Context(), srv.Client(), srv.URL, "v1.2.3", "linux", "amd64", target)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if b, _ := os.ReadFile(target); string(b) != "new binary" {
|
|
t.Errorf("target holds %q", b)
|
|
}
|
|
if b, _ := os.ReadFile(backup); string(b) != "old" {
|
|
t.Errorf("the old binary was not kept: %q", b)
|
|
}
|
|
|
|
// A download that does not match its checksum changes nothing.
|
|
bad := releaseServer(t, "v1.2.4", AssetName("v1.2.4", "linux", "amd64"), []byte("tampered"),
|
|
hex.EncodeToString(sum[:]))
|
|
if _, err := Install(t.Context(), bad.Client(), bad.URL, "v1.2.4", "linux", "amd64", target); err == nil {
|
|
t.Fatal("a binary not matching its checksum was installed")
|
|
}
|
|
if b, _ := os.ReadFile(target); string(b) != "new binary" {
|
|
t.Errorf("a failed install touched the binary: %q", b)
|
|
}
|
|
if _, err := Install(t.Context(), nil, srv.URL, "../../x", "linux", "amd64", target); err == nil {
|
|
t.Error("a version that is not one was fetched")
|
|
}
|
|
}
|
|
|
|
func TestReleasesAreFetchedOverHTTPSOrFromThisMachine(t *testing.T) {
|
|
for base, ok := range map[string]bool{
|
|
"https://gitea.example/releases/download": true,
|
|
"http://127.0.0.1:8080/r": true,
|
|
"http://localhost/r": true,
|
|
"http://gitea.example/releases/download": false,
|
|
"http://10.0.0.5/r": false,
|
|
"ftp://x/r": false,
|
|
"not a url": false,
|
|
} {
|
|
if err := CheckBase(base); (err == nil) != ok {
|
|
t.Errorf("%s: %v", base, err)
|
|
}
|
|
}
|
|
if _, err := Install(t.Context(), nil, "http://gitea.example/r", "v1.2.3", "linux", "amd64", "/nonexistent"); err == nil {
|
|
t.Error("a release over plain http was installed")
|
|
}
|
|
}
|
|
|
|
func TestFetchGivesOnlyACheckedBinary(t *testing.T) {
|
|
unsigned(t)
|
|
body := []byte("for another machine")
|
|
sum := sha256.Sum256(body)
|
|
asset := AssetName("v1.2.3", "linux", "arm64")
|
|
srv := releaseServer(t, "v1.2.3", asset, body, hex.EncodeToString(sum[:]))
|
|
got, err := Fetch(t.Context(), srv.Client(), srv.URL, "v1.2.3", "linux", "arm64")
|
|
if err != nil || string(got) != string(body) {
|
|
t.Fatalf("%q %v", got, err)
|
|
}
|
|
bad := releaseServer(t, "v1.2.3", asset, []byte("swapped"), hex.EncodeToString(sum[:]))
|
|
if _, err := Fetch(t.Context(), bad.Client(), bad.URL, "v1.2.3", "linux", "arm64"); err == nil {
|
|
t.Error("a binary not matching its checksum was given")
|
|
}
|
|
}
|