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:

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:

More details here and there .

Public SMB share

The defined privacy policy is:

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:

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

Kudos to all the people how made those docs available and / or answered my (dummy :) questions on the mailing-lists.