Git backed by object storage because you can't stop me
git object-storage kefka
10

Configure Feed

Select the types of activity you want to include in your feed.

feat(ssh): persist ed25519 host key in the bucket filesystem

Signed-off-by: Xe Iaso <me@xeiaso.net>

Xe Iaso (May 28, 2026, 11:12 PM EDT) a626c13c d1fec8cf

+127
+76
cmd/objgitd/ssh.go
··· 1 + package main 2 + 3 + import ( 4 + "crypto/ed25519" 5 + "crypto/rand" 6 + "encoding/pem" 7 + "errors" 8 + "fmt" 9 + "io" 10 + "log/slog" 11 + "os" 12 + "path/filepath" 13 + 14 + "github.com/go-git/go-billy/v6" 15 + gossh "golang.org/x/crypto/ssh" 16 + ) 17 + 18 + const hostKeyPath = ".objgit/ssh_host_ed25519_key" 19 + 20 + // loadOrCreateHostKey loads the server's ed25519 host key from fs, generating 21 + // and persisting one on first use so the key survives restarts. 22 + func loadOrCreateHostKey(fs billy.Filesystem) (gossh.Signer, error) { 23 + f, err := fs.Open(hostKeyPath) 24 + if err == nil { 25 + defer f.Close() 26 + pemBytes, err := io.ReadAll(f) 27 + if err != nil { 28 + return nil, fmt.Errorf("reading ssh host key: %w", err) 29 + } 30 + signer, err := gossh.ParsePrivateKey(pemBytes) 31 + if err != nil { 32 + return nil, fmt.Errorf("parsing ssh host key: %w", err) 33 + } 34 + return signer, nil 35 + } 36 + if !errors.Is(err, os.ErrNotExist) { 37 + return nil, fmt.Errorf("opening ssh host key: %w", err) 38 + } 39 + 40 + // Generate a new ed25519 key. 41 + _, priv, err := ed25519.GenerateKey(rand.Reader) 42 + if err != nil { 43 + return nil, fmt.Errorf("generating ssh host key: %w", err) 44 + } 45 + 46 + block, err := gossh.MarshalPrivateKey(priv, "") 47 + if err != nil { 48 + return nil, fmt.Errorf("marshaling ssh host key: %w", err) 49 + } 50 + pemBytes := pem.EncodeToMemory(block) 51 + 52 + // Persist the key. MkdirAll first in case the parent dir doesn't exist. 53 + if err := fs.MkdirAll(filepath.Dir(hostKeyPath), 0o700); err != nil && !errors.Is(err, os.ErrExist) { 54 + return nil, fmt.Errorf("creating ssh host key directory: %w", err) 55 + } 56 + 57 + wf, err := fs.Create(hostKeyPath) 58 + if err != nil { 59 + return nil, fmt.Errorf("creating ssh host key file: %w", err) 60 + } 61 + if _, err := wf.Write(pemBytes); err != nil { 62 + wf.Close() 63 + return nil, fmt.Errorf("writing ssh host key: %w", err) 64 + } 65 + if err := wf.Close(); err != nil { 66 + return nil, fmt.Errorf("closing ssh host key file: %w", err) 67 + } 68 + 69 + signer, err := gossh.ParsePrivateKey(pemBytes) 70 + if err != nil { 71 + return nil, fmt.Errorf("parsing generated ssh host key: %w", err) 72 + } 73 + 74 + slog.Info("created ssh host key", "path", hostKeyPath) 75 + return signer, nil 76 + }
+51
cmd/objgitd/ssh_test.go
··· 1 + package main 2 + 3 + import ( 4 + "bytes" 5 + "io" 6 + "testing" 7 + 8 + "github.com/go-git/go-billy/v6/memfs" 9 + ) 10 + 11 + func TestLoadOrCreateHostKey(t *testing.T) { 12 + fs := memfs.New() 13 + 14 + s1, err := loadOrCreateHostKey(fs) 15 + if err != nil { 16 + t.Fatalf("first call: %v", err) 17 + } 18 + 19 + // The key must have been persisted. 20 + f, err := fs.Open(hostKeyPath) 21 + if err != nil { 22 + t.Fatalf("host key not persisted at %s: %v", hostKeyPath, err) 23 + } 24 + first, err := io.ReadAll(f) 25 + f.Close() 26 + if err != nil { 27 + t.Fatal(err) 28 + } 29 + 30 + // A second call must reuse the same key, not regenerate. 31 + s2, err := loadOrCreateHostKey(fs) 32 + if err != nil { 33 + t.Fatalf("second call: %v", err) 34 + } 35 + if !bytes.Equal(s1.PublicKey().Marshal(), s2.PublicKey().Marshal()) { 36 + t.Error("second call returned a different key; expected the persisted one to be reused") 37 + } 38 + 39 + f2, err := fs.Open(hostKeyPath) 40 + if err != nil { 41 + t.Fatal(err) 42 + } 43 + second, err := io.ReadAll(f2) 44 + f2.Close() 45 + if err != nil { 46 + t.Fatal(err) 47 + } 48 + if !bytes.Equal(first, second) { 49 + t.Error("host key file changed on the second call; it must not be rewritten") 50 + } 51 + }