node: handle JWT secret read errors

This commit is contained in:
Minh Vu 2026-06-21 21:40:14 +02:00
parent e5ff3596db
commit d0f9d23b25
2 changed files with 61 additions and 1 deletions

View file

@ -342,10 +342,14 @@ func ObtainJWTSecret(fileName string) ([]byte, error) {
} }
log.Error("Invalid JWT secret", "path", fileName, "length", len(jwtSecret)) log.Error("Invalid JWT secret", "path", fileName, "length", len(jwtSecret))
return nil, errors.New("invalid JWT secret") return nil, errors.New("invalid JWT secret")
} else if !errors.Is(err, os.ErrNotExist) {
return nil, fmt.Errorf("failed to read JWT secret file %q: %w", fileName, err)
} }
// Need to generate one // Need to generate one
jwtSecret := make([]byte, 32) jwtSecret := make([]byte, 32)
crand.Read(jwtSecret) if _, err := crand.Read(jwtSecret); err != nil {
return nil, fmt.Errorf("failed to generate JWT secret: %w", err)
}
// if we're in --dev mode, don't bother saving, just show it // if we're in --dev mode, don't bother saving, just show it
if fileName == "" { if fileName == "" {
log.Info("Generated ephemeral JWT secret", "secret", hexutil.Encode(jwtSecret)) log.Info("Generated ephemeral JWT secret", "secret", hexutil.Encode(jwtSecret))

View file

@ -22,11 +22,14 @@ import (
"io" "io"
"net" "net"
"net/http" "net/http"
"os"
"path/filepath"
"reflect" "reflect"
"slices" "slices"
"strings" "strings"
"testing" "testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
@ -106,6 +109,59 @@ func TestNodeUsedDataDir(t *testing.T) {
} }
} }
func TestObtainJWTSecretGeneratesMissingFile(t *testing.T) {
path := filepath.Join(t.TempDir(), "jwtsecret")
secret, err := ObtainJWTSecret(path)
if err != nil {
t.Fatalf("failed to generate JWT secret: %v", err)
}
if len(secret) != 32 {
t.Fatalf("JWT secret length mismatch: have %d, want 32", len(secret))
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("failed to read generated JWT secret file: %v", err)
}
if have, want := string(data), hexutil.Encode(secret); have != want {
t.Fatalf("JWT secret file mismatch: have %q, want %q", have, want)
}
loaded, err := ObtainJWTSecret(path)
if err != nil {
t.Fatalf("failed to load generated JWT secret: %v", err)
}
if !slices.Equal(loaded, secret) {
t.Fatalf("loaded JWT secret mismatch: have %x, want %x", loaded, secret)
}
}
func TestObtainJWTSecretFailsOnReadError(t *testing.T) {
path := filepath.Join(t.TempDir(), "jwtsecret")
original := []byte("0x" + strings.Repeat("11", 32))
if err := os.WriteFile(path, original, 0200); err != nil {
t.Fatalf("failed to write JWT secret: %v", err)
}
t.Cleanup(func() {
os.Chmod(path, 0600)
})
if _, err := os.ReadFile(path); err == nil {
t.Skip("test requires unreadable files")
}
if _, err := ObtainJWTSecret(path); err == nil {
t.Fatal("generated JWT secret after read failure")
}
if err := os.Chmod(path, 0600); err != nil {
t.Fatalf("failed to restore JWT secret permissions: %v", err)
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("failed to read JWT secret file: %v", err)
}
if string(data) != string(original) {
t.Fatalf("JWT secret was overwritten: have %q, want %q", data, original)
}
}
// Tests whether a Lifecycle can be registered. // Tests whether a Lifecycle can be registered.
func TestLifecycleRegistry_Successful(t *testing.T) { func TestLifecycleRegistry_Successful(t *testing.T) {
stack, err := New(testNodeConfig()) stack, err := New(testNodeConfig())