feat(nfs): per-host NFS options (IP/CIDR with own ro/async/squash flags)
This is a backward-compatible MINOR bump (0.4.0 → 0.5.0).
BREAKING NOTES (for users upgrading from pre-0.5.0):
- The nfs_exports.clients column schema changed from []string to
[]NFSClient (per-host options). A migration (0005) transforms existing
string arrays into object arrays, taking export-level options as
defaults for each host.
- ValidateNFSClient now only accepts IPv4 (192.168.1.1) or IPv4/CIDR
(192.168.1.0/24). Hostnames, wildcards, netgroups are rejected.
- If you use NASCTL_IMPORT_ON_BOOT, re-import your /etc/exports to pick
up per-host options.
What changed:
- NFSClient type: {host, read_only, async, root_squash, subtree_check, advanced}
- NFSExport.Clients is now []NFSClient (was []string)
- export-level flags (ro/async/root_squash/subtree_check/advanced) are
preserved as template defaults for newly added hosts in the UI.
- buildExportLine generates: path host1(ro,sync,...) host2(rw,async,...) fsid=N
- ValidateNFSClient: strict IPv4/CIDR only (0-255 octets, /0-32 prefix)
- parseExportLine now parses per-host options from /etc/exports (previously
only the first host's options were kept, others were discarded)
- UI: per-host rows with toggles (ro/async/root_squash/subtree_check) and
advanced options (all_squash, secure, wdelay, hide, crossmnt)
This commit is contained in:
+25
-3
@@ -13,7 +13,7 @@ var (
|
||||
usernamePattern = regexp.MustCompile(`^[a-z_][a-z0-9_-]*[$]?$`)
|
||||
shareNamePattern = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
|
||||
nfsOptionPattern = regexp.MustCompile(`^[a-z_]+$`)
|
||||
nfsClientPattern = regexp.MustCompile(`^[a-zA-Z0-9_.:/\-\*]+$`)
|
||||
nfsClientPattern = regexp.MustCompile(`^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(/\d{1,2})?$`)
|
||||
allowedNFSOptions = map[string]bool{
|
||||
"rw": true, "ro": true, "sync": true, "async": true,
|
||||
"root_squash": true, "no_root_squash": true, "all_squash": true,
|
||||
@@ -108,13 +108,35 @@ func ValidatePathAllowed(path string, allowedRoots []string) error {
|
||||
}
|
||||
|
||||
// ValidateNFSClient checks an NFS client/network specifier.
|
||||
// Accepts IPv4 addresses (e.g. 192.168.1.20) and IPv4 CIDR (e.g. 192.168.1.0/24).
|
||||
func ValidateNFSClient(client string) error {
|
||||
if client == "" || len(client) > 255 {
|
||||
return fmt.Errorf("invalid nfs client length")
|
||||
}
|
||||
if !nfsClientPattern.MatchString(client) {
|
||||
return fmt.Errorf("invalid nfs client: %q", client)
|
||||
|
||||
var ipPart string
|
||||
var prefix int = 32
|
||||
|
||||
if idx := strings.IndexByte(client, '/'); idx >= 0 {
|
||||
ipPart = client[:idx]
|
||||
if _, err := fmt.Sscanf(client[idx+1:], "%d", &prefix); err != nil || prefix < 0 || prefix > 32 {
|
||||
return fmt.Errorf("invalid nfs client CIDR prefix %q (must be 0-32): %q", client[idx+1:], client)
|
||||
}
|
||||
} else {
|
||||
ipPart = client
|
||||
}
|
||||
|
||||
octets := strings.Split(ipPart, ".")
|
||||
if len(octets) != 4 {
|
||||
return fmt.Errorf("invalid nfs client %q: must be IPv4 or IPv4/CIDR", client)
|
||||
}
|
||||
for _, o := range octets {
|
||||
var n int
|
||||
if _, err := fmt.Sscanf(o, "%d", &n); err != nil || n < 0 || n > 255 {
|
||||
return fmt.Errorf("invalid nfs client %q: octet %q out of range (0-255)", client, o)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user