fix(nodetoken): stop refusing every node-token key file on Windows

FileKeySource rejected any key file whose mode had group or other bits,
but Windows has no such bits: Stat reports every writable file as 0666.
On the Windows builds release.yml ships, the key file therefore never
loaded, not even one written 0600, and only XUI_NODE_TOKEN_KEY could
supply a key. The mode check now applies off Windows only, the stance
the DB permission tests already take; there the file's NTFS ACL guards
it, and env-vars.mdx says so in all four locales.

The load test is split so the half that must hold everywhere, an
owner-only file loading, also runs on Windows, and the rejection half
asserts the exact error instead of any error.
This commit is contained in:
MHSanaei
2026-09-27 15:55:42 +02:00
parent ff322f901a
commit 3fc3992a46
6 changed files with 36 additions and 15 deletions
+4 -2
View File
@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"os"
"runtime"
"strings"
)
@@ -76,7 +77,8 @@ func decodeKey(b64 string) ([keyLen]byte, error) {
return out, nil
}
// FileKeySource accepts only key files that are mode 0600 or stricter.
// FileKeySource accepts only key files that are mode 0600 or stricter. Windows has
// no such bits (Stat reports 0666), so there the file's NTFS ACL is what guards it.
type FileKeySource struct {
Path string
}
@@ -86,7 +88,7 @@ func (f FileKeySource) Load() (*Keyring, error) {
if err != nil {
return nil, fmt.Errorf("nodetoken: stat key file %s: %w", f.Path, err)
}
if perm := info.Mode().Perm(); perm&0o077 != 0 {
if perm := info.Mode().Perm(); runtime.GOOS != "windows" && perm&0o077 != 0 {
return nil, fmt.Errorf("nodetoken: key file %s has insecure mode %#o (want 0600)", f.Path, perm)
}
data, err := os.ReadFile(f.Path)
+28 -9
View File
@@ -3,8 +3,10 @@ package nodetoken
import (
"encoding/base64"
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
@@ -212,21 +214,26 @@ func TestParseMode(t *testing.T) {
}
}
func TestFileKeySourceRejectsLoosePerms(t *testing.T) {
dir := t.TempDir()
p := filepath.Join(dir, "k.json")
// writeKeyFile writes a one-key keyring and chmods it, since WriteFile's mode
// passes through the umask.
func writeKeyFile(t *testing.T, mode os.FileMode) string {
t.Helper()
p := filepath.Join(t.TempDir(), "k.json")
key := make([]byte, keyLen)
body, _ := json.Marshal(keyFile{Active: "k1", Keys: map[string]string{"k1": base64.StdEncoding.EncodeToString(key)}})
if err := os.WriteFile(p, body, 0o644); err != nil {
if err := os.WriteFile(p, body, mode); err != nil {
t.Fatal(err)
}
if _, err := (FileKeySource{Path: p}).Load(); err == nil {
t.Fatal("0644 key file must be rejected")
}
if err := os.Chmod(p, 0o600); err != nil {
if err := os.Chmod(p, mode); err != nil {
t.Fatal(err)
}
kr, err := (FileKeySource{Path: p}).Load()
return p
}
// Windows reports every writable file as 0666, so a mode check there refused
// every key file, an owner-only one included.
func TestFileKeySourceLoadsOwnerOnlyKeyFile(t *testing.T) {
kr, err := (FileKeySource{Path: writeKeyFile(t, 0o600)}).Load()
if err != nil {
t.Fatalf("0600 key file should load: %v", err)
}
@@ -235,6 +242,18 @@ func TestFileKeySourceRejectsLoosePerms(t *testing.T) {
}
}
func TestFileKeySourceRejectsLoosePerms(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("POSIX permission bits are not meaningful on Windows")
}
p := writeKeyFile(t, 0o644)
_, err := (FileKeySource{Path: p}).Load()
want := fmt.Sprintf("nodetoken: key file %s has insecure mode 0644 (want 0600)", p)
if err == nil || err.Error() != want {
t.Fatalf("Load() error = %v, want %q", err, want)
}
}
func TestEnvKeySource(t *testing.T) {
key := make([]byte, keyLen)
for i := range key {