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
160 lines
4.4 KiB
Go
160 lines
4.4 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package nfs4server
|
|
|
|
import (
|
|
"net"
|
|
"testing"
|
|
|
|
"sourcedock.dev/petrbalvin/nfs/internal/rpc"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/server"
|
|
|
|
"sourcedock.dev/petrbalvin/nfs/internal/krb5"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfs4"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfsclient"
|
|
)
|
|
|
|
// The three RPCSEC_GSS service levels round trip against the real TCP
|
|
// client: the credential sequence window, the verifier MIC over the
|
|
// call header, the checksummed arguments and results at integrity and
|
|
// the sealed ones at privacy.
|
|
func TestKerberosServiceLevels(t *testing.T) {
|
|
h := testTree(t)
|
|
key := make([]byte, 32)
|
|
for i := range key {
|
|
key[i] = byte(i + 1)
|
|
}
|
|
h.ServerKey = key
|
|
h.ServiceName = "nfs"
|
|
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
srv := &server.Server{Handle: h.HandleConn}
|
|
go srv.Serve(t.Context(), ln)
|
|
defer ln.Close()
|
|
|
|
cases := []struct {
|
|
name string
|
|
svc uint32
|
|
}{
|
|
{"krb5", rpc.SvcNone},
|
|
{"krb5i", rpc.SvcIntegrity},
|
|
{"krb5p", rpc.SvcPrivacy},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
cl, err := nfsclient.Dial(ln.Addr().String())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cl.Close()
|
|
// The session exists before the GSS switch: the COMPOUND
|
|
// then carries SEQUENCE under the GSS credential.
|
|
if err := cl.Establish("gss-" + tc.name); err != nil {
|
|
t.Fatalf("establish: %v", err)
|
|
}
|
|
if err := cl.EnableGSS(krb5.EtypeAES256, key, "EXAMPLE.ORG", "nfs",
|
|
"petr@EXAMPLE.ORG", tc.svc); err != nil {
|
|
t.Fatalf("enable gss: %v", err)
|
|
}
|
|
res, bodies, err := cl.Compound("gss", [][]byte{
|
|
nfs4.AppendPutRootfh(nil),
|
|
nfs4.AppendGetattr(nil, nfs4.OfBits(nfs4.AttrType, nfs4.AttrSize)),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("compound: %v", err)
|
|
}
|
|
if res.Status != nfs4.ErrOK || len(bodies) != 2 {
|
|
t.Fatalf("compound: status %d bodies %d", res.Status, len(bodies))
|
|
}
|
|
// A second call walks the sequence window one further.
|
|
res, _, err = cl.Compound("gss2", [][]byte{
|
|
nfs4.AppendPutRootfh(nil),
|
|
nfs4.AppendLookup(nil, "a.txt"),
|
|
})
|
|
if err != nil || res.Status != nfs4.ErrOK {
|
|
t.Fatalf("second compound: status %d %v", res.Status, err)
|
|
}
|
|
if err := cl.DisableGSS(); err != nil {
|
|
t.Fatalf("disable: %v", err)
|
|
}
|
|
// After the destroy the client falls back to AUTH_SYS and the
|
|
// compound succeeds anonymously again.
|
|
res2, _, err2 := cl.Compound("after", [][]byte{nfs4.AppendPutRootfh(nil)})
|
|
if err2 != nil || res2.Status != nfs4.ErrOK {
|
|
t.Fatalf("compound after disable: %d %v", res2.Status, err2)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// RPCSEC_GSSv3: the CREATE control procedure binds assertions to a
|
|
// child handle and the compounds under the child carry the version
|
|
// three credential, RFC 7861.
|
|
func TestGSSv3CreateAndUse(t *testing.T) {
|
|
h := testTree(t)
|
|
key := make([]byte, 32)
|
|
for i := range key {
|
|
key[i] = byte(i + 9)
|
|
}
|
|
h.ServerKey = key
|
|
h.ServiceName = "nfs"
|
|
|
|
addr := startCBServer(t, h)
|
|
cl, err := nfsclient.Dial(addr)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cl.Close()
|
|
if err := cl.Establish("v3"); err != nil {
|
|
t.Fatalf("establish: %v", err)
|
|
}
|
|
if err := cl.EnableGSS(krb5.EtypeAES256, key, "EXAMPLE.ORG", "nfs",
|
|
"petr@EXAMPLE.ORG", rpc.SvcIntegrity); err != nil {
|
|
t.Fatalf("enable gss: %v", err)
|
|
}
|
|
|
|
// CREATE with a label assertion over the parent context.
|
|
child, err := cl.CreateGSSChild([]rpc.Assertion{{
|
|
Type: rpc.AssertionLabel,
|
|
Label: rpc.Label{
|
|
LfsId: 1,
|
|
PiId: 0,
|
|
Bytes: []byte("secret"),
|
|
},
|
|
}})
|
|
if err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
if len(child) == 0 {
|
|
t.Fatal("no child handle")
|
|
}
|
|
|
|
// A compound under the child handle rides the version three
|
|
// credential at the integrity level.
|
|
res, _, err := cl.Compound("v3", [][]byte{
|
|
nfs4.AppendPutRootfh(nil),
|
|
nfs4.AppendLookup(nil, "a.txt"),
|
|
})
|
|
if err != nil || res.Status != nfs4.ErrOK {
|
|
t.Fatalf("compound under child: status %d %v", res.Status, err)
|
|
}
|
|
|
|
// The server bound the label to the child context.
|
|
if lbl := h.labelOf(child); lbl == nil || string(lbl.Bytes) != "secret" {
|
|
t.Fatalf("label not bound: %+v", lbl)
|
|
}
|
|
|
|
// LIST answers the supported assertion types.
|
|
types, err := cl.ListGSSAssertions()
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(types) != 2 {
|
|
t.Fatalf("list types %v", types)
|
|
}
|
|
}
|