by: Helge, published: Jul 7, 2024, updated: Sep 14, 2024, in

Samba File Server With Windows ACLs in a Docker Container

This article explains how to set up a Samba file server with Windows ACLs as a domain member in a Docker container. This post is part of my series on home automation, networking & self-hosting that shows how to install, configure, and run a home server with dockerized or virtualized services.

This article is part of a mini-series about running Samba Active Directory and a file server service in a Docker container on a home server:

Please read the previous articles of this mini-series before proceeding.

Dockerized Samba File Server Installation

If you followed my Samba Active Directory domain controller installation guide, you already have a Samba file server in a Docker container. The only thing left to do is configure it.

Supervisord Config File supervisord.conf

Create config-fs1/supervisor/supervisord.conf with the following content:

[supervisord]
nodaemon=true
user=root
logfile=/dev/null
logfile_maxbytes=0

[program:smbd]
command=smbd --foreground --no-process-group --debug-stdout
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
startretries=1
autorestart=false

[program:winbindd]
command=winbindd --foreground --no-process-group --debug-stdout
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
startretries=1
autorestart=false

Start the Fileserver Container

Verify that when you start the Samba containers you see the following when you inspect the logs with docker compose logs fs1:

No Samba config file found at: /etc/samba/config/smb.conf
Please exec into the container and join a domain by running the following commands:
  docker exec -it samba bash
  /usr/helpers/samba-join.sh

Domain Join

Exec into the FS container and run the domain join script:

docker exec -it fs1 bash
/usr/helpers/samba-join.sh

After the file server has joined the domain, restart the FS container to give my init script the chance to do its magic at container start:

docker compose stop fs1
docker compose start fs1

Inspect the container logs for errors with the command docker compose logs fs1. You should see the following:

INFO Set uid to user 0 succeeded
INFO supervisord started with pid 7
INFO spawned: 'smbd' with pid 11
INFO spawned: 'winbindd' with pid 12
smbd version 4.19.5-Ubuntu started.
Copyright Andrew Tridgell and the Samba Team 1992-2023
winbindd version 4.19.5-Ubuntu started.
Copyright Andrew Tridgell and the Samba Team 1992-2023
INFO success: smbd entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
INFO success: winbindd entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)

Verification

Test Samba Configuration

Test the Samba config file by running testparm.

DNS Record

Make sure that the file server’s name can be resolved on other machines on the network:

host fs1.ad.internal

Winbindd

Run the following commands to verify that Winbindd is able to connect to the AD DC:

wbinfo --ping-dc
# Expected output: checking the NETLOGON for domain[AD] dc connection to "dc1.ad.internal" succeeded

wbinfo -u
# Expected output: [list of users in the domain]

wbinfo -g
# Expected output: [list of groups in the domain]

wbinfo -i administrator
# Expected output: [passwd entry for administrator]

getent passwd administrator
# Expected output: [passwd entry for administrator]

getent group "AD\Domain Users"
# Expected output: domain users:x:10513:

Re-Join

Before you can re-join a machine to AD you need to delete its DNS record or dynamic DNS updates will fail because only the previous machine SID has permissions to update the existing record.

Run the following on the DC:

# Usage
samba-tool dns delete <server> <zone> <name> <A|AAAA|PTR|CNAME|NS|MX|SRV|TXT> <data>
# Example
samba-tool dns delete localhost ad.internal fs1 A 192.168.0.6 -U administrator

Files Shares

Create a File Share

The Docker compose file has a bind mount for file shares that is visible in the container as /srv/samba. To create a file share data, perform the following steps (via exec in the fs1 container):

  1. Create a directory in the file system:
    mkdir /srv/samba/data
    
  2. Set Linux permissions on the shared folder. This is necessary because effective permissions for Windows users are determined by combining Linux permissions with Windows ACLs: both must allow the requested access. Since we want effective permissions to be governed by Windows ACLs, only, we set full control (777) on Linux.
    chmod 777 /srv/samba/data
    
  3. Set the Linux owner and group to Domain Admins and Domain Users, respectively:
    chown "domain admins":"domain users" /srv/samba/data
    
  4. Add the share definition as a new stanza to config/smb.conf:
    [Data]
    	path = /srv/samba/data
    	read only = no
       
    	# VFS modules
    	vfs objects = acl_xattr
    
    	# Ignore the standard Unix permissions; don't show them in Windows ACL Editor.
    	acl_xattr:ignore system acls = yes
    
  5. Reload the Samba configuration:
    smbcontrol all reload-config
    

Map (Mount) a File Share From Windows

To map a Samba file share on a Windows machine it’s not necessary for the Windows machine to be joined to the Samba domain. Run the following command to map the Data share on any Windows PC connected to the same network:

# When prompted, enter:
# 1) The user name in the format: AD\USERNAME
# 2) The user's password
# Optionally replace S: with a different drive letter
net use s: \\fs1.ad.internal\data /savecred /p:yes

Note: The password is stored securely in Windows Credential Manager (open GUI with control keymgr.dll). The stored credentials persist reboots; they are automatically re-used when the network drive is being re-connected.

SeDiskOperatorPrivilege: Allow a User/Group to Manage Share Permissions

By default, only the members of domain member’s local group Administrators can manage file share permissions. That is generally a sensible configuration that shouldn’t be changed. I’m only mentioning this here because the SeDiskOperatorPrivilege privilege creeps up in many Samba guides on the internet.

Set Windows File System Permissions (ACLs) on Linux

You should be able to manage shares and permissions from a domain-joined Windows machine (docs). On machines that are not domain-joined, user/group name lookup seems to be difficult. If you don’t join your Windows PCs to the Samba domain (I don’t), you can manage permissions from Linux, too.

Note that I only cover file system permissions here because as a rule of thumb you shouldn’t use share-level permissions.

Directory Structure

Before diving into the fun topic of ACLs, we’ll need to decide on a directory structure.

I want to map one network drive only. The base directory of that shared folder is to contain directories that would otherwise be shared individually. File system permissions govern access to those directories on level 1 in the shared folder. Example:

/srv/samba/data/		<<-- base dir (shared folder)
 ├── Family-Data/		<<-- level 1 in shared folder
 ├── Pictures/			<<-- level 1 in shared folder
 └── Videos/			<<-- level 1 in shared folder

Tools for Managing Windows Permissions on Linux

Samba comes with two different tools aimed at managing Windows file system ACLs on Linux.

Samba-Tool NTAcl

The Swiss army knife samba-tool with the option ntacl allows you to get and set permissions as SDDL strings locally on a Samba file server. Its main downside is not that you’re forced to deal with SDDL but that it seems to be incompatible with the Samba option acl_xattr:ignore system acls. In my testing I found that it adds Linux UGO permissions to the Windows ACL. The result can only be described as impractical.

Smbcacls

smbcacls is Samba’s equivalent to Windows’ icacls.exe. It provides a much friendlier access to ACLs and – most importantly – doesn’t throw Linux UGO permissions into the mix. That makes it the tool of choice for anyone who needs to manage Windows ACLs on a Samba file server.

List Windows ACLs on Linux

The following command lists the default permissions on our newly-shared directory from a Windows point of view:

smbcacls //localhost/data / -U USERNAME

Note: do not use the Administrator user in the smbcacls command or you’ll get the error code NT_STATUS_INVALID_TOKEN.

Create Groups to Assign Permissions

As a best practice, always assign permissions to groups specifically created for that purpose. Create one group that grants full permissions and another that grants read permissions only. To do so for our shared data directory run the following in the DC container:

# Read & write
samba-tool group add FS_Data_RW --groupou="OU=Groups,OU=My"
# Read only
samba-tool group add FS_Data_R --groupou="OU=Groups,OU=My"

Set Windows ACLs on Linux

With all the pieces in place, we can now set permissions:

smbcacls //localhost/data / -U USERNAME --set "REVISION:1,OWNER:AD\Domain Admins,GROUP:AD\Domain Users,ACL:AD\Domain Admins:ALLOWED/OI|CI/FULL,ACL:AD\Domain Users:ALLOWED/0x0/R,ACL:AD\FS_Data_RW:ALLOWED/OI|CI/FULL,ACL:AD\FS_Data_R:ALLOWED/OI|CI/READ"

Notes:

  • The set command replaces (deletes) existing ACLs.
  • Set requires REVISION, OWNER, and GROUP.
  • Set fails with NT_STATUS_ACCESS_DENIED if you’re not the directory owner (either directly or via group membership).

What About Linux Owner/Group/Other Permissions?

We don’t have to worry about the Linux permissions. Samba sets those to the user creating a file/directory and the group domain users, respectively. Linux permissions are set to 770 (full control for user and group).

Permissions on Level 1 in the Shared Folder

Repeat the following process to create each directory on level 1 within the shared folder with the appropriate permissions.

Create the Directory

On a Windows machine (!), create the L1 directory, e.g., Pictures. If you create the directory on Linux, Windows permissions are not inherited from the parent directory.

Create the Groups

Back on Linux, create two domain groups for setting permissions:

samba-tool group add FS_Data_Pictures_RW --groupou="OU=Groups,OU=My"
samba-tool group add FS_Data_Pictures_R --groupou="OU=Groups,OU=My"

Add Permissions

Add an access control entry (ACE) to the ACL that already contains ACEs inherited from the parent directory:

smbcacls //localhost/data /Pictures -U USERNAME --add "ACL:AD\FS_Data_PICTURES_RW:ALLOWED/OI|CI/FULL,ACL:AD\FS_Data_Pictures_R:ALLOWED/OI|CI/READ" [--propagate-inheritance]

If Pictures is not empty (ie., it contains files or subfolders), add the --propagate-inheritance parameter.

Automatic Propagation of Permissions to Subfolders and Files

Windows ACL Editor automatically propagates ACEs to subfolders and files recursively (according to each ACE’s inheritance options, of course). Smbcacls doesn’t. That’s why it’s necessary to explicitly add the --propagate-inheritance parameter.

Additional Features & Optimizations

Recycle Bin

Samba comes with a recycle bin feature that moves files to a .recycle directory instead of deleting them. This feature is independent of the Windows File Explorer recycle bin.

Notes

  • With Samba’s recycle bin enabled, there is no way to “really” delete files (e.g., with Shift+Del) without moving them to the .recycle directory.
  • Just like the Windows original, Samba’s recycle bin is not emptied automatically.
  • The .recycle directory is created with the hidden attribute so that’s it’s not visible in Windows File Explorer by default.

Enable Recycle Bin

To enable Samba’s recycle bin, add the following lines to a share configuration in smb.conf:

	# VFS modules (make sure to include all modules - this is not cumulative to a global definition but replaces it instead)
	vfs objects = recycle acl_xattr
	
	# Recycle bin configuration
	recycle:directory_mode = 775
	recycle:keeptree = yes
	recycle:versions = yes

Set Permissions

Files moved to the .recycle directory don’t keep their original Windows permissions. Instead, they inherit the Windows permissions from the .recycle directory. You may want to grant additional read permissions as follows:

smbcacls //localhost/data .recycle -U USERNAME --add "ACL:AD\Domain Users:ALLOWED/OI|CI/READ" --propagate-inheritance

Delete Files After 60 Days

To prevent the recycle bin from growing indefinitely we’ll set up a script on the Docker host that runs daily and deletes files and directories in the bin after 60 days.

Create the file /usr/local/bin/samba-recycle-cleanup.sh with the following content:

#!/bin/bash
RecyclePath=/rpool/encrypted/docker/samba/shares-fs1/data/.recycle
MaxDays=60
# Delete all files created earlier than MaxDays
find $RecyclePath -name "*" -ctime +$MaxDays -type f -delete
# Delete empty directories
find $RecyclePath -name "*" -empty -type d -delete

Make the script executable:

chmod +x /usr/local/bin/samba-recycle-cleanup.sh

Create a cron job to run the script daily one minute after midnight:

crontab -e

Paste the following into the file that opens:

1 0 * * * /usr/local/bin/samba-recycle-cleanup.sh

Access-Based Enumeration (ABE)

What is Access-Based Enumeration (ABE)?

Access-based enumeration is a popular Microsoft technology that has existed in Windows Server since 2003. ABE displays only those files and folders a user has permission to access. It does this by filtering directory listings when a network share is accessed via SMB, removing files or folders the user doesn’t have read permissions for.

Enabling Access-Based Enumeration (ABE) in Samba

Samba has an equivalent to ABE that can be enabled by adding the following to the share definition stanza in smb.conf:

	# Enable access-based enumeration
	hide unreadable = yes

Note: this works well because we’ve enabled acl_xattr:ignore system acls. Otherwise, it would also consider Linux permissions, which would make this feature impractical.

Shadow Copies as Previous Versions

Samba’s vfs_shadow_copy2 module exposes Linux file system snapshots as Windows shadow copies that can be accessed as “previous versions” in File Explorer.

Warning: the configuration described below works, but it causes errors: ZFS snapshots cannot be deleted anymore as soon as they’ve been access through Windows’ Previous Versions.

Install and Configure zfs-auto-snapshot

Install the auto-snapshotting script zfs-auto-snapshot:

apt install zfs-auto-snapshot

Note that this also installs cron jobs in /etc/cron.*. You can list them with la /etc/cron.*/zfs-auto*.

I don’t want snapshots for the various rpool/ROOT/pve-1/* datasets, so I changed the script’s default behavior to only snapshot datasets where the property com.sun:auto-snapshot has been explicitly set to true. To do so, edit each cron job file, appending --default-exclude. Example:

*/15 * * * * root which zfs-auto-snapshot > /dev/null || exit 0 ; zfs-auto-snapshot --quiet --syslog --label=frequent --keep=4 --default-exclude //

Enable snapshots for the dataset where Samba data is stored:

zfs set com.sun:auto-snapshot=true rpool/encrypted

Enable and Configure Samba’s shadow_copy2 Module

To enable Samba’s recycle bin, add the following lines to a share configuration in smb.conf:

# Enable recycle bin and shadow copies
vfs objects = recycle shadow_copy2
# Shadow copy configuration
shadow:snapdir = /.zfs/snapshot
shadow:snapsharepath = docker/samba/shares-fs1/data
shadow:snapprefix = zfs
shadow:delimiter = -20
shadow:format = -%Y-%m-%d-%H%M

Mount the Snapshot Directory in the Docker Container

Add the following volume definition to docker-compose.yml:

    volumes:
      - /rpool/encrypted/.zfs/snapshot:/.zfs/snapshot:ro

Error: Cannot Destroy Snapshot: Dataset is Busy

It seems that whenever a Windows client accesses a “previous version” (aka, a ZFS snapshot), that snapshot cannot be deleted anymore. If that happens for a “frequent” snapshot, you may get one email from Cron every 15 minutes with a message like the following:

cannot destroy snapshot rpool/encrypted@zfs-auto-snap_frequent-2024-06-23-2145: dataset is busy

I am not aware of a solution to this issue.

Disable the Print Service

To disable print server functionality, add the following to the [global] stanza of the machine’s config/smb.conf file:

load printers = no
printing = bsd
printcap name = /dev/null
disable spoolss = yes

Note: this is also a useful configuration for domain controllers.

Backup

My backup solution based on restic works with this Samba file server setup very well, indeed.

Running Samba in an Unprivileged Container

Historically, Samba had be run in a privileged Docker container because it stores Windows permissions in the security.NTACL extended attribute, and the security.* namespace is only accessible as root.

As of Samba 4.18, an alternative location can be specified via the acl_xattr:security_acl_name configuration option (docs).

Troubleshooting

Enable Samba Logging

To enable Samba logging, add the following to the [global] stanza of smb.conf:

	# Increase the level up to 10 for more detail
	log level = 3

Reload Samba’s configuration with smbcontrol all reload-config.

The log output is redirected to Docker. Inspect it on the host:

docker compose logs CONTAINER_NAME --timestamps

Resources

Changelog

2024-09-09

  • Improved the script samba-recycle-cleanup.sh.

2024-07-22

Missing Supervisord Config File

  • Added the section Supervisord Config File supervisord.conf.

2024-07-10

Map (Mount) a File Share From Windows

  • Changed the net use command so that credentials are stored in Windows Credential Manager, persisting a reboot.
  • Added command to access Credential Manager’s UI.

Previous Article Samba Active Directory as Authelia's SSO Authentication Backend
Next Article Samba File Server: Web Access Through Filebrowser With SSO & HTTPS