SMB shares using OmniOS, zones and ZFS
2414 words, 12 minutes
OmniOS / Illumos provides a native way to expose data stored on ZFS using the SMB / CIFS protocol. Furthemore, using zones limits the attack surface of a server ; or a least, the impact of a compromised service.
Long story short: I replaced my UFS+Samba shares with ZFS+Solarish.
Dedicated zone
I’d rather limit the running services on the global zone. Thus I decided to dedicate a zone and some ZFS datasets to the hosting of two kinds of data:
- public is a dataset with data that is managed by a single user but is accessible read-only to anyone. Those are mostly family pictures and movies that I keep and manage but we want to watch for the Apple TV.
- timemachine is the dataset hosting macOS Time Machine and rsnapshot backups. It is basically a private dataset with specialized mDNS announcement.
ZFS datasets are created from the Global Zone (GZ) but will be managed later on from the Non Global Zone (NGZ).
# zfs create -o mountpoint=/public tank/public
# zfs create -o mountpoint=/timemachine tank/timemachine
The zone will be very similar to the GZ so I use a sparse one. The datasets are attached using the dataset resource type.
# zadm create -b sparse samba
{
"autoboot" : "true",
"brand" : "sparse",
"dataset" : [
{ "name" : "tank/public" },
{ "name" : "tank/timemachine" }
],
"dns-domain" : "example",
"ip-type" : "exclusive",
"net" : [ { "global-nic" : "igb0", "physical" : "samba0" } ],
"resolvers" : [ "9.9.9.9", "1.1.1.1" ],
"zonename" : "samba",
"zonepath" : "/zones/samba"
}
Start the Zone and log in to continue the configuration.
# zadm start samba
# zlogin samba
The attached ZFS datasets can be seen both from the GZ and the NGZ. But their content can only be managed from one or the other. In the current state, the dataset will appear empty from the GZ although it can be seen. Until the NGZ is destroyed and the dataset reattached to the GZ, this zone will manage the dataset.
The IP configuration can be done during zadm
usage. But for reasons,
I’m using DHCP fixed reservations. So I configure DHCP inside the Zone.
# dladm show-link
LINK CLASS MTU STATE BRIDGE OVER
samba0 vnic 1500 up -- ?
# ipadm create-addr -T dhcp samba0/v4
# ifconfig samba0
samba0: flags=1004843<UP,BROADCAST,RUNNING,MULTICAST,DHCP,IPv4> mtu 1500 index 2
inet 192.0.2.2 netmask ffffff00 broadcast 192.0.2.255
ether 2:8:10:5e:b4:a2
SMB using built-in Illumos and ZFS features
I have been using SAMBA for decades. But the good news is that Illumos provides a ZFS / kernelbased SMB server with full NFSv4 ACL support. Unlike with SAMBA, you need to operate with datasets in mind, not directories.
Enable the SMB Service
The smbd(8) daemon manages CIFS/SMB requests. Start the daemon using SMF.
# svcadm enable -r smb/server
# svcs -d smb/server
STATE STIME FMRI
online 23:05:58 svc:/milestone/network:default
online 23:06:00 svc:/system/filesystem/local:default
online 23:30:59 svc:/system/idmap:default
online 23:30:59 svc:/network/smb/client:default
# sharectl status
smbfs online client
autofs online client
nfs disabled
smb online
Configure global options using sharectl(8) and smb(5) properties.
# sharectl set -p system_comment="Illumos SMB/CIFS Service" smb
# sharectl set -p netbios_enable=true smb
# sharectl get smb
system_comment=Illumos CIFS Service
max_workers=1024
netbios_enable=true
(...)
# sharectl get -p min_protocol -p max_protocol -p encrypt smb
min_protocol=
max_protocol=
encrypt=disabled
Announce the SMB Service
Multicast DNS and DNS Service Discovery allows LAN machines to discover the SMB shares. The service has to be enabled.
# svcadm enable dns/multicast
Advertisement works by running DNS-SD commands.
# /usr/bin/dns-sd -R "$(hostname -s)" _smb._tcp local 445 &
Using macOS, a default monitor icon is displayed. The icon can be changed to better illustrate the service.
# /usr/bin/dns-sd -R "$(hostname -s)" _device-info._tcp local 445 model=Xserve &
There are a few available icons. Depending on your mood, you can choose
between: Xserve PowerBook PowerMac Macmini iMac MacBook MacBookPro
MacBookAir MacPro AppleTV1,1 AirPort TimeCapsule6
More can be found by digging in
/System/Library/CoreServices/\ CoreTypes.bundle/Contents/Info.plist
and
/System/Library/CoreServices/\ CoreTypes.bundle/Contents/Resources
on a
macOS system.
Those commands won’t be run at startup by default. One should rather use
the Service Management Privileges system. But I don’t know (yet) how to
do this. So I’m using the quick & dirty init.d
system.
# cat > /etc/init.d/smb-announce
#!/sbin/sh
#
# Announce SMB share service
case "$1" in
'start')
/usr/bin/dns-sd -R "$(hostname -s)" _smb._tcp local 445 &
/usr/bin/dns-sd -R "$(hostname -s)" _device-info._tcp local 445 model=Xserve &
;;
'stop')
pkill dns-sd
;;
*)
echo "Usage: $0 { start | stop }"
exit 1
;;
esac
exit 0
#EOF
^D
# chmod 0744 /etc/init.d/smb-announce
# /etc/init.d/smb-announce start
# ln -s /etc/init.d/smb-announce /etc/rc3.d/S99smb-announce
# ln -s /etc/init.d/smb-announce /etc/rcS.d/K99smb-announce
Manage Groups and Users
The SMB service can either join a workgroup or an Active Directory domain. At $HOME, I only use the default WORKGROUP.
The service uses SMB local groups and users. Password management using
passwd
is enabled through PAM.
# vi /etc/pam.conf
(...)
other password required pam_smb_passwd.so.1
I only use a few groups and users at $HOME. Each family member has its user. A guest read-only account is used for third-party applications.
# groupadd -g 100 users
# useradd -u 1000 -g users -d /nonexistent -s /usr/bin/false \
-c "Joel Carnat" jca
# passwd jca
passwd: password successfully changed for jca
passwd: SMB password successfully changed for jca
# smbadm enable-user jca
jca is enabled.
# groupadd -g 60100 guest
# useradd -u 60100 -g guest -d /nonexistent -s /usr/bin/false \
-c "SMB guest" guest
# smbadm enable-user guest
guest is enabled.
Enable SMB shares
Right now, only some default shares are available.
(laptop)# smbclient -L 192.0.2.2 -U guest -N
Sharename Type Comment
--------- ---- -------
c$ Disk Default Share
IPC$ IPC Remote IPC
SMB1 disabled -- no workgroup available
ACL pit stop
Studying Using ACLs to Protect ZFS
Files
and the
manpage for acl
is highly recommended. I am used to the POSIX model
using chmod
and umask
on an UFS filesystem. But dealing with
permissions on a ZFS filesystem exposed by SMB is a bit different.
Disclaimer: I apologize to any Solaris/Illumos/NFSv4/ZFS experts that would read the following. I’m trying to make it simple and clear from a newcomer POV. I hope it is just not plain wrong :)
Basically, the ZFS access privileges correspond to the UFS permissions
and the umask behaviour is driven by the inheritance flags. To have a
proper (expected) behaviour, chmod
should be used with NFSv4 ACLs.
Here are the rules I keep in mind when configuring ACLs:
- ACLs are processed in order. From top to bottom as displayed by
ls -V
. - ACLs will match any rule based on the “who”. If the user is part of a group and a group ACL is defined before a user or owner ACL, it will match.
- Once a permission has been granted, it can’t be denied later on. So “Deny” ACL should come first.
More details here and there .
Public SMB share
The defined privacy policy is:
- A single user is the owner and has full management powers on the data.
- Any authenticated user can read-only the data.
- Any unauthenticated user (guest) can read-only the data.
Configure and expose the share using sharemgr(8) properties.
# zfs set sharesmb=name=Public,guestok=true tank/public
# sharemgr show -pv
default nfs=()
zfs smb=()
zfs/tank/public smb=()
/public
Public=/public smb=(guestok="true")
smb smb=()
* /var/smb/cvol smb=() ""
c$=/var/smb/cvol smb=(abe="false" guestok="false") "Default Share"
Configure the ownership:
# chown jca /public
# chgrp users /public
In smb access mode, the default behaviour regarding files is that they
get the same permissions as directories. The umask
UNIX behaviour does
not seem to apply. I don’t want my file to get the execute
bit on this
particular share. The ACLs that I configure enforce this behaviour.
# chmod A0=\
owner@:rwxp-DaARWcCos:d:allow,\
owner@:rw-p--aARWcCos:f:allow,\
group@:r-x---a-R-c--s:d:allow,\
group@:r-----a-R-c--s:f:allow,\
everyone@:r-x---a-R-c--s:d:allow,\
everyone@:r-----a-R-c--s:f:allow \
/public
From an external machine, check authenticated permissions:
(laptop)# smbclient '\\192.0.2.2\Public' -U 'jca'
Password for [WORKGROUP\jca]:
Try "help" to get a list of possible commands.
smb: \> prompt
smb: \> recurse
smb: \> put RPi4_UEFI_Firmware_v1.31.zip
putting file RPi4_UEFI_Firmware_v1.31.zip as \RPi4_UEFI_Firmware_v1.31.zip (22847.3 kb/s) (average 22847.3 kb/s)
smb: \> mput Example
putting file Example/n1cur41w.iso as \Example\n1cur41w.iso (27318.6 kb/s) (average 27180.6 kb/s)
putting file Example/myscript.sh as \Example\myscript.sh (7.0 kb/s) (average 27142.8 kb/s)
putting file Example/invoice.pdf as \Example\invoice.pdf (7616.0 kb/s) (average 27084.1 kb/s)
putting file Example/icons-set.tgz as \Example\icons-set.tgz (18424.6 kb/s) (average 27016.6 kb/s)
putting file Example/SlideMe.odp as \Example\SlideMe.odp (28462.7 kb/s) (average 27126.9 kb/s)
putting file Example/Playlist.xml as \Example\Playlist.xml (13605.1 kb/s) (average 27075.5 kb/s)
putting file Example/ThinkPad.ods as \Example\ThinkPad.ods (18381.1 kb/s) (average 27006.3 kb/s)
putting file Example/Macbook.xlsx as \Example\Macbook.xlsx (2357.1 kb/s) (average 26985.7 kb/s)
putting file Example/Doc Template.odt as \Example\Doc Template.odt (18970.0 kb/s) (average 26953.9 kb/s)
putting file Example/A text file.txt as \Example\A text file.txt (0.0 kb/s) (average 26925.9 kb/s)
smb: \> ls
. DA 0 Sun Dec 10 13:46:51 2023
.. 0 Thu Jan 1 01:00:00 1970
RPi4_UEFI_Firmware_v1.31.zip A 3111626 Sun Dec 10 13:46:46 2023
Example DA 0 Sun Dec 10 13:46:55 2023
\Example
. DA 0 Sun Dec 10 13:46:55 2023
.. DA 0 Sun Dec 10 13:46:51 2023
icons-set.tgz A 641474 Sun Dec 10 13:46:55 2023
myscript.sh A 43 Sun Dec 10 13:46:55 2023
invoice.pdf A 101385 Sun Dec 10 13:46:55 2023
Macbook.xlsx A 9655 Sun Dec 10 13:46:55 2023
A text file.txt A 0 Sun Dec 10 13:46:55 2023
Doc Template.odt A 369083 Sun Dec 10 13:46:55 2023
ThinkPad.ods A 715248 Sun Dec 10 13:46:55 2023
n1cur41w.iso A 116764672 Sun Dec 10 13:46:55 2023
A directory D 0 Sun Dec 10 13:46:55 2023
Playlist.xml A 250771 Sun Dec 10 13:46:55 2023
SlideMe.odp A 10492496 Sun Dec 10 13:46:55 2023
\Example\A directory
. D 0 Sun Dec 10 13:46:55 2023
.. DA 0 Sun Dec 10 13:46:55 2023
59034211 blocks of size 131072. 59033767 blocks available
smb: \> exit
From an external machine, check unauthenticated permissions:
(laptop)# smbclient '\\192.0.2.2\Public' -U 'guest' -N
Try "help" to get a list of possible commands.
smb: \> ls
. DA 0 Sun Dec 10 13:46:51 2023
.. 0 Thu Jan 1 01:00:00 1970
RPi4_UEFI_Firmware_v1.31.zip A 3111626 Sun Dec 10 13:46:46 2023
Example DA 0 Sun Dec 10 13:46:55 2023
59034214 blocks of size 131072. 59033601 blocks available
smb: \> put omnios-r151048.iso
NT_STATUS_ACCESS_DENIED opening remote file \omnios-r151048.iso
smb: \> exit
Time Machine SMB share
The defined privacy policy is:
- Every authenticated user can create and manage it’s own Time Machine backups.
- Users can only see the backups they own.
- Unauthenticated users (guest) are not allowed on this share.
Configure the Time Machine dataset:
# zfs set \
sharesmb=name=TimeMachine,description="Time Machine",guestok=false,abe=true \
tank/timemachine
# sharemgr show -pv
default nfs=()
smb smb=()
* /var/smb/cvol smb=() ""
c$=/var/smb/cvol smb=(abe="false" guestok="false") "Default Share"
zfs smb=()
zfs/tank/public smb=()
/public
Public=/public smb=(guestok="true")
zfs/tank/timemachine smb=()
/timemachine
TimeMachine=/timemachine smb=(guestok="false" abe="true") "Time Machine"
The name and description properties are displayed by SMB clients. The ABE (access-based enumeration) policy ensure users only see files and directories they can access.
Apply the privacy policy by setting permissions and ACLs on the dataset:
# chgrp users /timemachine
# chmod A0=\
owner@:execute:file_inherit/inherit_only:deny,\
owner@:full_set:file_inherit/dir_inherit:allow,\
group@:full_set:file_inherit/dir_inherit/inherit_only:deny,\
group@:full_set::allow,\
everyone@:full_set:file_inherit/dir_inherit:deny \
/timemachine
Expecting mDNS to be already configured, an extra step can be done to advertise the Time Machine share to macOS machines.
# /usr/bin/dns-sd -R "$(hostname -s)" _adisk._tcp local 445 \
'sys=waMa=0,adVF=0x100' 'dk0=adVN=TimeMachine,adVF=0x82' &
# vi /etc/init.d/smb-announce
(...)
'start')
/usr/bin/dns-sd -R "$(hostname -s)" \
_smb._tcp local 445 &
/usr/bin/dns-sd -R "$(hostname -s)" \
_device-info._tcp local 445 model=Xserve &
/usr/bin/dns-sd -R "$(hostname -s)" \
_adisk._tcp local 445 'sys=waMa=0,adVF=0x100' \
'dk0=adVN=TimeMachine,adVF=0x82' &
;;
(...)
The value for adVN corresponds to the one set using sharemgr
‘-r’ parameter.
From there, macOS machines should be able to connect to the share a run their
Time Machine jobs. One could check ACLs using an smbclient
program:
(laptop)# smbclient -U guest -N '\\192.0.2.2\TimeMachine'
tree connect failed: NT_STATUS_ACCESS_DENIED
(laptop)# smbclient -U jca '\\192.0.2.2\TimeMachine'
Password for [WORKGROUP\jca]:
Try "help" to get a list of possible commands.
smb: \> ls
. DA 0 Sun Dec 10 15:07:47 2023
.. 0 Thu Jan 1 01:00:00 1970
59033585 blocks of size 131072. 59033582 blocks available
smb: \> exit
# smbclient '\\10.15.5.54\TimeMachine' -U 'cca'
Password for [WORKGROUP\cca]:
Try "help" to get a list of possible commands.
smb: \> ls
. DA 0 Sun Dec 10 15:08:39 2023
.. 0 Thu Jan 1 01:00:00 1970
.DS_Store AH 6148 Sun Dec 10 15:05:08 2023
MacBook Pro.sparsebundle DA 0 Sun Dec 10 15:08:50 2023
59033579 blocks of size 131072. 59032916 blocks available
smb: \> exit
(omnios)# find /timemachine -ls
34 11 drwxrwx---+ 4 root users 5 Dec 10 14:08 /timemachine
176 1 -rw-------+ 1 cca users 6148 Dec 10 14:05 /timemachine/.DS_Store
3 1 drwxr-x--- 2 root sys 3 Dec 10 12:59 /timemachine/.$EXTEND
4 1 -rw-rw-rw-+ 1 root sys 0 Dec 10 12:59 /timemachine/.$EXTEND/$QUOTA
298 2 drwx------+ 4 cca users 11 Dec 10 14:08 /timemachine/MacBook Pro.sparsebundle
427 1 -rw-------+ 1 cca users 502 Dec 10 14:03 /timemachine/MacBook Pro.sparsebundle/Info.bckup
429 1 -rw-------+ 1 cca users 0 Dec 10 14:03 /timemachine/MacBook Pro.sparsebundle/token
434 1 -rw-------+ 1 cca users 516 Dec 10 14:08 /timemachine/MacBook Pro.sparsebundle/com.apple.TimeMachine.MachineID.bckup
(...)
Delete Zone and recover data
If the NGZ should be destroyed, the data can still be kept and reattached either to the GZ or to any other NGZ you like. For example, if you use the NGZ for testing purpose and want to expose the dataset from the GZ.
Delete the NGZ:
# zadm stop samba
# zadm delete samba
Attach the datasets back to the GZ
# zfs get zoned tank/public tank/timemachine
NAME PROPERTY VALUE SOURCE
tank/public zoned on local
tank/timemachine zoned on local
# zfs inherit zoned tank/public tank/timemachine
# zfs mount -a
# zfs get zoned tank/public tank/timemachine
NAME PROPERTY VALUE SOURCE
tank/public zoned off default
tank/timemachine zoned off default
Look at the datasets’ content. Note the unknown users and groups ID.
# ls -alh /public/ /timemachine/
/public/:
total 6099
drwxr-xr-x+ 4 1000 100 5 Dec 10 13:46 .
drwxr-xr-x 2 root sys 3 Dec 9 18:06 .$EXTEND
drwxr-xr-x 34 root root 35 Dec 8 14:27 ..
drwxr-xr-x+ 3 1000 100 13 Dec 10 13:46 Example
-rw-r--r-- 1 1000 100 2.97M Dec 10 13:46 RPi4_UEFI_Firmware_v1.31.zip
/timemachine/:
total 32
drwxrwx---+ 4 root 100 5 Dec 10 15:08 .
drwxr-x--- 2 root sys 3 Dec 10 13:59 .$EXTEND
drwxr-xr-x 34 root root 35 Dec 8 14:27 ..
-rw-------+ 1 1001 100 6.00K Dec 10 15:05 .DS_Store
drwx------+ 4 1001 100 11 Dec 10 15:08 MacBook Pro.sparsebundle
Bibliography
- Using ACLs to Protect ZFS Files
- Configuring SMB in SmartOS
- Which tool to manage CIFS shares
- SAMBA on SmartOS using delegated datasets
Kudos to all the people how made those docs available and / or answered my (dummy :) questions on the mailing-lists.