28 March, 2020

How to run a "simple" Samba Podman container under Fedora 31


This is really just a "note to self" to document how I set-up a basic home-share from a Samba daemon running in a Podman container under Fedora 31. Unfortunately doing such a notionally simple task involves getting a bunch of slightly finicky security and isolation systems to cohabit in just the right way.

So what's the problem?

Samba itself was reverse-engineered from the Microsoft SMB protocol, which was not so much "designed" as "cultivated" over time. As such, it's a pretty complicated beast and if you don't configure it exactly the right way, its usual feedback mechanism is to simply not work.

On top of that, Fedora 31 has selinux and firewalld on by default. These two security systems do fairly simple things, but are similarly unhelpful in communicating what you're doing wrong.

Finally the Podman container management system apes the commandline interface of the common Docker system (but in a safer fork-based paradigm vs the daemon-based one of Docker). While it's a really close mirror of Docker, certain functionality is different in places and it's effectively "emulating" the Docker commandline.

I also want to run this all through Systemd services so the daemon can be stopped, started and queried on demand (And started by the OS on boot-up).

These things all add-up to "complexity", which makes for much head-scratching.

Sample script

Note: This works for me. You could probably tweak all the numbers and paths to your own liking
firewall-cmd --add-service --permanent samba
firewall-cmd --add-service samba

comment="Shared files"


podman run --name ${container_name} -p 139:139 -p 445:445 -v "${data_src}:${data_dst}:Z" -e USERID=${c_uid} -e GROUPID=${c_gid} -d dperson/samba -n -s "${share_name};${data_dst};${browseable};${readonly};${guest};${users};${admins};${writelist=none};${comment}" -u "${user};${password}" -p -g "map to guest = bad user" -g "load printers = no" -g "printcap cache time = 0" -g "printing = bsd" -g "printcap name = /dev/null" -g "disable spoolss = yes"

podman generate systemd --name "${container_name}" >"${service_file}"
systemctl enable ${service}
systemctl start ${service}

Script breakdown

firewall-cmd --add-service --permanent samba
firewall-cmd --add-service samba

These two lines tell firewalld to open the necessary ports for Samba now and after any future reboots.

Now we set-up some configurable parameter variables that can be customised for your own install:

  • container_name=samba - The name for the container we're creating
  • data_src=/data - This is the path to the directory in the host OS that we want to share
  • data_dst=/share - The name of the directory to both mount the data_src dir within the container and to also share-out to clients.
  • c_uid=65534 - The user ID to run the Samba process as inside the container (see notes below)
  • c_gid=65534 - The group ID to run the Samba process as inside the container
  • share_name=client - The name of the share to publish
  • browseable=yes - Can this share be listed on the server
  • readonly=yes - Only for users not in the writelist setting.
  • guest=yes - Whether anonymous/passwordless access is allowed
  • comment="Shared files" - An informational comment for the clients describing the share
Ther user list settings are comma-separated lists of user names and can use the special names all and none.
  • users=all - List of user names allowed access (exceeding the guest flag)
  • admins=none - List of users allowed administration operations
  • writelist=none - List of user allowed write access to shares with the
This will be the name for the running container. I set a variable because I use it as the base for the service name and the service configuration file later on.
podman run --name ${container_name}
Run a new container and name it "samba" (So far, so simple). The next set of parameters modify the environment of the container.
-p 139:139 -p 445:445 -p 137:137/udp -p 138:138/udp
Map through the 2 TCP and 2 UDP ports needed for Samba. I don't need to do any remapping, so the internal/external numbers are the same.
-v "/media/share/data/shared:/share:Z"
Map a volume from the host OS path "/media/share/data/shared" to be "/share" inside the container. The trailing "Z" component tells Podman to apply an selinux label to the share path indicating it's for "private" use (i.e. no other container can use it). You might alternatively want to use "z" (lowercase) here to indicate other containers can access the share. If you don't specify either "z" or "Z", you're likely to hit an selinux denial to access your share.
-e USERID=1002 -e GROUPID=64003
The default behaviour of this container image is to run the Samba daemon as "uid=100(smbuser) gid=101(smb)". Unfortunately this doesn't match the UIDs on the host OS, so I set these two environment variables that the "/usr/bin/samba.sh" script inside the container uses to modify the "smbuser" user to match the external IDs. I did try using one of the three-ish methods Podman allows you to do UID mapping, but after several iterations and four additional arguments to the command line, the daemon would keep failing-out some time after launch with an error indicating it had his some incompatibility with later kernels (Sorry - I forget exactly what it was now). In any case, this method doesn't require any live ID-remapping and might even be a little more efficient.
Interestingly I can't find any explicit documentation for this parameter in the Podman docs. It is however a clone of the Docker "detach" option, which means the container will exit when the main process exits.
This is the name of the container image to use from the Docker Hub. It's a lightweight Samba server with a helpful and reasonably terse commandline interface.

All the following parameters are handed to the main process of the docker image itself.
-s "${share_name};${data_dst};${browseable};${readonly};${guest};${users};${admins};${writelist=none};${comment}"
This parameter definesone file share from the paramaters we defined above. You can add several of these parameters to add more shared directories (with extra parameters or hardcoded values).

-u "${user};${password}" (Optional additions: ";${ID};${group};${GID}"

This defines a user with a password (in clear text), which can be used in the user list parameters above. I'm not wntirely sure how the ID, group and GID parameters are supposed to work, as they had no discernable effect on the test shares I set up (All new entries were owned by the default USERID/GROUPID values set in the -e parameters to Podman).
Set ownership and permissions on the shares. This will run a recursive chown operation from the wrapper script on the share directory(/ies).

Now I continue with a series of general config parameters ("-g") that are given to the Samba daemon.
  • usershare allow guests = yes - This is a second way to say I want guest access?
  • map to guest = bad user - If a client presents an unknown user name, consider them a guest.
The next five parameters are here because SMB is a file and printer sharing protocol, but I don't want the printer-sharing part of it. Samba doesn't have any explicit way of turning this off, but The Internet tells me these parameters together make it care very little about printing.
  • load printers = no
  • printcap cache time = 0
  • printing = bsd
  • printcap name = /dev/null
  • disable spoolss = yes
So that's all the Samba and Podman stuff out of the way. I now just have to make a Systemd service out of it.
These variables just set the service name and the corresponding service definition file name, based on the container name I defined before.
podman generate systemd --name "${container_name}" >"${service_file}"
This is one of Podman's cool little tricks. It generates a Systemd service definition file for a named container. So I can now start, stop and query the "container-samba" service.
systemctl enable ${service}
systemctl start ${service}
The "enable" statement just creates some symlinks to the service definition file I just made so that on the next boot, it can be started automatically. The "start" statement at this time doesn't do much (The container's already running), but it does mean that Systemd is in-sync with its state now.

So that's all of it - Clear as mud!

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.