Test / test (push) Successful in 2m4s
Release / gates (push) Successful in 2m5s
Release / build (amd64, freebsd) (push) Successful in 1m27s
Release / build (amd64, linux) (push) Successful in 1m22s
Release / build (amd64, netbsd) (push) Successful in 1m19s
Release / build (amd64, openbsd) (push) Successful in 1m20s
Release / build (arm64, darwin) (push) Successful in 1m21s
Release / build (arm64, freebsd) (push) Successful in 1m26s
Release / build (arm64, linux) (push) Successful in 1m25s
Release / build (arm64, netbsd) (push) Successful in 1m31s
Release / build (arm64, openbsd) (push) Successful in 1m27s
Release / build (loong64, linux) (push) Successful in 1m37s
Release / build (riscv64, linux) (push) Successful in 1m21s
Release / release (push) Successful in 40s
Assisted-by: GLM 5.3 Flash
112 lines
3.4 KiB
Go
112 lines
3.4 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package nfs4server_test
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"math/big"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfs4"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfs4server"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfsclient"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfsfs"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/server"
|
|
)
|
|
|
|
// benchCert mints one self signed certificate for the TLS tests.
|
|
func benchCert(t *testing.T) tls.Certificate {
|
|
t.Helper()
|
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
t.Fatalf("key: %v", err)
|
|
}
|
|
tmpl := x509.Certificate{
|
|
SerialNumber: big.NewInt(1),
|
|
Subject: pkix.Name{CommonName: "nfs-test"},
|
|
NotBefore: time.Now().Add(-time.Hour),
|
|
NotAfter: time.Now().Add(time.Hour),
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
|
DNSNames: []string{"localhost"},
|
|
}
|
|
der, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &key.PublicKey, key)
|
|
if err != nil {
|
|
t.Fatalf("certificate: %v", err)
|
|
}
|
|
return tls.Certificate{Certificate: [][]byte{der}, PrivateKey: key}
|
|
}
|
|
|
|
// serveTLS starts the handler behind a loopback listener with the given
|
|
// certificate and returns the address.
|
|
func serveTLS(t *testing.T, cert tls.Certificate) string {
|
|
t.Helper()
|
|
root := t.TempDir()
|
|
backend, err := nfsfs.NewLocal(root)
|
|
if err != nil {
|
|
t.Fatalf("NewLocal: %v", err)
|
|
}
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("Listen: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = ln.Close() })
|
|
handler := &nfs4server.Handler{FS: backend,
|
|
TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}}}
|
|
go func() { _ = (&server.Server{Handle: handler.HandleConn}).Serve(t.Context(), ln) }()
|
|
return ln.Addr().String()
|
|
}
|
|
|
|
// TestWireTLSSession drives a full session over an in place TLS upgrade:
|
|
// the probe answers the STARTTLS token, the handshake runs, and a
|
|
// compound rides the encrypted connection.
|
|
func TestWireTLSSession(t *testing.T) {
|
|
addr := serveTLS(t, benchCert(t))
|
|
cl, err := nfsclient.Dial(addr)
|
|
if err != nil {
|
|
t.Fatalf("Dial: %v", err)
|
|
}
|
|
defer cl.Close()
|
|
if err := cl.EnableTLS(&tls.Config{InsecureSkipVerify: true, ServerName: "localhost"}); err != nil {
|
|
t.Fatalf("EnableTLS: %v", err)
|
|
}
|
|
if err := cl.Establish("tls-test"); err != nil {
|
|
t.Fatalf("Establish: %v", err)
|
|
}
|
|
res, _, err := cl.Compound("tls-getattr", [][]byte{
|
|
nfs4.AppendPutRootfh(nil),
|
|
nfs4.AppendGetattr(nil, nfs4.OfBits(nfs4.AttrType)),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Compound: %v", err)
|
|
}
|
|
if res.Status != nfs4.ErrOK {
|
|
t.Fatalf("getattr status %d over TLS", res.Status)
|
|
}
|
|
}
|
|
|
|
// TestWirePlaintextRefusedUnderTLS covers the RFC 9289 policy: a client
|
|
// that skips STARTTLS and runs procedures in plaintext is refused with
|
|
// auth too weak, while the NULL procedure still answers.
|
|
func TestWirePlaintextRefusedUnderTLS(t *testing.T) {
|
|
addr := serveTLS(t, benchCert(t))
|
|
cl, err := nfsclient.Dial(addr)
|
|
if err != nil {
|
|
t.Fatalf("Dial: %v", err)
|
|
}
|
|
defer cl.Close()
|
|
if err := cl.Null(); err != nil {
|
|
t.Fatalf("the plaintext NULL no longer answers: %v", err)
|
|
}
|
|
if err := cl.Establish("plaintext-test"); err == nil {
|
|
t.Fatal("a plaintext session was accepted under a TLS certificate")
|
|
}
|
|
}
|