Showing posts with label mount. Show all posts
Showing posts with label mount. Show all posts

Thursday, March 09, 2023

Docker container user cannot write to shared volume

Something that has been baffling me for a while was how to mount a volume in a docker container such that a specific user inside said container would have the same group ID (gid) as a given user outside the container. But, before we delve into that, let's simplify the problem a bit with a few assumptions:

Assumptions

  • The "user outside the container" is a user in the docker server. This user can run docker (is in the docker group). We shall call this user outbob, with uid = gid = 1500
  • The "user inside the container" is a user in the docker container, which is created by the Dockerfile. This user will be known as inbob, with uid = gid = 1995
  • This is a bare example, the smallest proof of concept I could come up with. I may update this article later with a link to a practical application as soon as I shove it in my github account.

The Problem

Let's create a really simple Dockerfile that shows the problem in /tmp/bob for no reason whatsoever.

outbob@dockerbox:/tmp/bob$ cat Dockerfile
# Set the base image to Ubuntu
FROM ubuntu

ENV DEVUSER inbob
ENV DEVID 1995

# Create user
RUN useradd -m --shell /bin/bash -u $DEVID $DEVUSER

USER $DEVUSER
ENV WD /home/${DEVUSER}
WORKDIR ${WD}
outbob@dockerbox:/tmp/bob$

We build the image from that Dockerfile as usual:

outbob@dockerbox:/tmp/bob$ docker build -t bob .
Sending build context to Docker daemon  2.048kB
Step 1/7 : FROM ubuntu
 ---> 27941809078c
Step 2/7 : ENV DEVUSER inbob
 ---> Running in e8501dbe8398
[...]
 ---> Running in 963671a09a5f
Removing intermediate container 963671a09a5f
 ---> a6049000bfda
Successfully built a6049000bfda
Successfully tagged bob:latest
outbob@dockerbox:/tmp/bob$

And run it, passing the same directory we used because I am not in the mood of being original. Yes, I could pass the command I wanted to execute directly from the docker run statement instead of starting bash and then running the command. Deal with it.

outbob@dockerbox:/tmp/bob$ docker run -i --rm -v /tmp/bob:/bob -t bob bash
inbob@3d7c097a38d1:~$ id
uid=1995(inbob) gid=1995(inbob) groups=1995(inbob)
inbob@3d7c097a38d1:~$ exit
exit
outbob@dockerbox:/tmp/bob$ 

So far so good. Now what happens if I try to become an user with the same uid and gid as outbob?

outbob@dockerbox:/tmp/bob$ docker run -i --rm  -u $(id -u):$(id -g) -v /tmp/bob:/bob -t bob bash
groups: cannot find name for group ID 1500
I have no name!@c4355f38747a:/home/inbob$ id
uid=1500 gid=1500 groups=1500
I have no name!@c4355f38747a:/home/inbob$ cd /bob
I have no name!@c4355f38747a:/bob$ touch nose
I have no name!@c4355f38747a:/bob$ ls -lh
total 4.0K
-rw-r--r-- 1 1500 1500 200 Feb 14 01:01 Dockerfile
-rw-r--r-- 1 1500 1500   0 Feb 14 18:31 nose
I have no name!@c4355f38747a:/bob$ id inbob
uid=1995(inbob) gid=1995(inbob) groups=1995(inbob)
I have no name!@c4355f38747a:/bob$

It works in that I can write to the volume but I am now a completely different user; a user with no name. Since I am not Clint Eastwood, I would rather be inbob. Is there a solution?

The Solution (so far)

Note: if you do not want to cut-n-paste the following excerpts, I also put this code in a repo.

We stablished I do not want to find out inbob created something in /tmb/bob as its default gid. The cleanest solution I found so far is to add inbob to the same group outbob used to create /tmb/bob, and then ensure anyone belonging to that group can write to this directory as a member of that group. So, let's do whatever I just said!

Set the directory in question to inherit the gid

We will set the setgid attribute for the directory:

outbob@dockerbox:/tmp/bob$ ls -ld /tmp/bob
drwxr----- 2 outbob outbob 4096 Mar  9 08:55 /tmp/bob/
outbob@dockerbox:/tmp/bob$
outbob@dockerbox:/tmp/bob$ chmod 2770 /tmp/bob
outbob@dockerbox:/tmp/bob$
outbob@dockerbox:/tmp/bob$ ls -ld /tmp/bob
drwxrws--- 2 outbob outbob 4096 Mar  9 08:55 /tmp/bob/
outbob@dockerbox:/tmp/bob$

The little s after the group permissions indicate we have been successful.

Configure the docker build to add user to proper group at runtime

We need to change the Dockerfile a bit and then create a docker-entrypoint.sh file:

outbob@dockerbox:/tmp/bob$ cat Dockerfile
# Set the base image to Ubuntu
FROM ubuntu

ENV DEVUSER inbob
ENV DEVUID 1995
ENV DEVGID 1995
ENV EXTGID 1999

# Create user
RUN useradd -m --shell /bin/bash -u $DEVUID $DEVUSER

# USER $DEVUSER
ENV WD /home/${DEVUSER}
WORKDIR ${WD}

# Put the entrypoint script somewhere we can find
COPY docker-entrypoint.sh /entrypoint.sh
RUN chmod 0700 /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

outbob@dockerbox:/tmp/bob$ cat docker-entrypoint.sh
#!/bin/sh
set -e

groupadd -g $EXTGID extgroup
adduser $DEVUSER extgroup

su - $DEVUSER

# And we are done here
exec "$@"
outbob@dockerbox:/tmp/bob$

Test time!

After we buld the new image we run it passing the default gid for outbob. Nothing stopping us from passing a different gid if it is what we need; docker does not care.

outbob@dockerbox:/tmp/bob$ docker run -i --rm  -e EXTGID=$(id -g) -v /tmp/bob:/bob -t bob bash
Adding user `inbob' to group `extgroup' ...
Adding user inbob to group extgroup
Done.
inbob@2a7339336f3e:~$ cd /bob
inbob@2a7339336f3e:/bob$ ls -l
total 8
-rw-r--r-- 1 5000 extgroup 389 Mar  9 13:42 Dockerfile
-rw-r--r-- 1 5000 extgroup 122 Mar  9 13:48 docker-entrypoint.sh
inbob@2a7339336f3e:/bob$ touch nose
inbob@2a7339336f3e:/bob$ ls -l
total 8
-rw-r--r-- 1    5000 extgroup 389 Mar  9 13:42 Dockerfile
-rw-r--r-- 1    5000 extgroup 122 Mar  9 13:48 docker-entrypoint.sh
-rw-rw-r-- 1 inbob extgroup   0 Mar  9 14:00 nose
inbob@2a7339336f3e:/bob$ exit
logout
root@2a7339336f3e:/home/inbob# exit
exit
outbob@dockerbox:/tmp/bob$ ls -l
total 8
-rw-r--r-- 1 outbob outbob 122 Mar  9 08:48 docker-entrypoint.sh
-rw-r--r-- 1 outbob outbob 389 Mar  9 08:42 Dockerfile
-rw-rw-r-- 1 1995 outbob   0 Mar  9 09:00 nose
outbob@dockerbox:/tmp/bob$ rm nose
outbob@dockerbox:/tmp/bob$

Other than getting out is now a two-step process, which may not be important when running a container in the background, the only telltale we were up to not good is that the uid for the file we created does not match any in our docker server. But, outbob can still delete the file.

And that is how we bring harmony in the divided worlds of bob.

Wednesday, October 03, 2018

Creating and mounting a fileshare as a file in a KVM Windows guest

Yeah, the title is a mouthfull.

So, let's get it done.

  1. Create the disk, which is henceforth called foretest.iso. I made it to be 1G because, as the name says, it is a test.
    raub@vmhost:~$ dd if=/dev/zero of=foretest.iso bs=1G count=1
    1+0 records in
    1+0 records out
    1073741824 bytes (1.1 GB, 1.0 GiB) copied, 2.29044 s, 469 MB/s
    raub@vmhost:~$
    NOTE: Some people would complain that I am using the .iso extension for my image file instead of, say, .img. Well, this is my article; deal with it.
  2. Now let's feed it to the vm.
    raub@vmhost:~$ virsh attach-disk desktop dev/foretest.iso hda --type raw --mode readwrite                 
    error: No support for readwrite in command 'attach-disk'
    
    raub@vmhost:~$

    Well, I did not know my KVM host does not have attach-disk. I guess I need to use attach-device instead. But, we will need to use a config file to describe how we want the storage to look like. If it looks familiar, we have used attach-disk to mount a USB device, namely an APC UPS.

    So, here is the .xml file:

    cat > dev/foretest.xml << 'EOF'
    <disk type='file' device='disk'>
       <driver name='qemu' type='raw' cache='none'/>
       <source file='/home/raub/dev/foretest.iso'/>
    <target dev='hdc'/>
    </disk >
    EOF

    And here we are feeding it into the guest:

    raub@vmhost:~$ sudo virsh attach-device --config testdesktop dev/foretest.xml
    Device attached successfully
    
    raub@vmhost:~$

    So we should be able to go to the guest and see the drive waving at us, right? Er, not quite. it is being listed here

    <disk type='file' device='disk'>
    
          <source file='/home/raub/dev/foretest.iso'/>
          <target dev='vdd' bus='virtio'/>
          <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
       </disk>

    But not in the GUI thingie nor inside the guest. And, yes, I have 2 virtual CD drives in this guest.


On a second thought, maybe I am doing this wrong. You see, the --config option tells it to add the drive to the config file. Only way to enable that is to completely shut down the vm guest (rebooting is not enough) and then restart it. What if I use --live instead, which does the deed in real time, as if I just pop the computer on and add the drive to an unused sata port?

raub@vmhost:~$ sudo virsh attach-device --live testdesktop dev/foretest.xml
error: Failed to attach device from dev/foretest.xml
error: internal error unable to execute QEMU command '__com.redhat_drive_add': Device 'drive-virtio-disk3' could not be initialized

raub@vmhost:~$

I guess it does not like me to add hard drives to a running guest. We need to try something else.

Attempt #2: USB

What if we pretend our disk is really a removable device like a USB drive? We begin by rewriting our .xml file:

cat > dev/foretest.xml << 'EOF'
<disk type='file' device='disk'>
  <driver name='qemu' type='raw'/>
  <source file='/home/raub/dev/foretest.iso'/>
  <target dev='sdd' bus='usb'/>
</disk>
EOF

Now let's mount it:

raub@desktop:~$ sudo virsh attach-device --live testdesktop dev/foretest.xml
Device attached successfully

raub@desktop:~$

So far so good. Can our guest see it this time? When I use Windows Explorer (do notice I disabled one of the CD drives as I was doing something else between the last screen capture and this one) I do not see the drive there; I kinda expected Windows to say something like "Hey! You just connected an unformatted drive! I need to format it!" Oh well; there are more than one way to get this done:

There it is! And we can format it!

And then, we can put things into it.

Now, let's unmount it

raub@desktop:~$ sudo virsh detach-device --live testdesktop dev/foretest.xml
Device detached successfully

raub@desktop:~$

Now let's make it a physical drive!

Stupid question: since we pretend our foretest.iso file is a USB drive, can we make it a real USB drive? We would test that question by first grabbing a USB drive which is at least 1GB in size (I had a 16GB one doing nothing), which was mounted into my KVM host as /dev/sdh.And now we need to copy the file to the drive.

raub@desktop:~$ sudo dd if=dev/foretest.iso of=/dev/sdh
raub@desktop:~$

Then grab USB drive and then mount it in a windows physical desktop. And it will look just like how it looks in the testdesktop guest down to be able to write to it.

The next step will be to see if we can make it a system disk, a disk we can boot a computer from. But, that will be for another article.

Wednesday, October 25, 2017

Can't find the Windows drive I want to mount a fileshare in

If you have read this blog before, you know I am not a Microsoft Windows fanboy. For those who have not read it before, let's talk about one of its annoying features: the use of drive letters to make fileshares available to the user. I understand the idea of using drive letters... in the 80s personal computers since they had at best two floppy drives or if you were really rich a floppy and a hard drive. During those bad hairstyle times, you only needed some way to differentiate two devices, so why not call them A and B or 0 and 1? But we now live in a time where a lot of people in our planet do not even know what a floppy drive is (hint: it is not a sex toy) and might connect a phone, a portable storage device of some kind, and who knows what will come next? 26 "drives" might run out quickly. Also,
why must we mount a new device/fileshare at the top level ("drive") instead of in a directory inside another fileshare like you do in UNIX (including Linux and OSX)?

The dirty little secret is that you can, but most users who grew up using Windows are so used to drive letters they cannot think of the option. And, there are Windows programs out there which can only handle drive letters. Point is, Microsoft is not to blame. In fact, they have been trying to convince people to use Microsoft (duh!) UNC Paths, which might not follow the path convention used by every other operating system out there but it is a huge improvement from the drive letter thingie. So, credit to where credit is due.

But, this article is not about path conventions and the religion around them. All we want to do is mount a fileshare inside another in windows. Humble goal, yes? Well, as the poem says, "The best laid schemes of mice and men / Often go awry."

I can has mah driv?

There are instructions out there to mount a fileshare without using drive letters in Windows; the one I picked uses the Computer Management GUI thingie, which might be important since coworkers are paralytic fearful of the command line:




Why is it not showing the D: drive? After all, diskpart can see it:

PS C:\Users\raub.adm> diskpart

Microsoft DiskPart version 6.3.9600

Copyright (C) 1999-2013 Microsoft Corporation.
On computer: Srv12R2

DISKPART> list volume

  Volume ###  Ltr  Label        Fs     Type        Size     Status     Info
  ----------  ---  -----------  -----  ----------  -------  ---------  --------
  Volume 0     F   New Volume   NTFS   Simple        99 GB  Healthy
  Volume 1     D   Data         NTFS   Simple      2959 GB  Healthy
  Volume 2     C   Srv12R2      NTFS   Simple        59 GB  Healthy    Boot
  Volume 3         System Rese  NTFS   Simple       350 MB  Healthy    System
  Volume 4     E   Utility      NTFS   Simple        61 GB  Healthy
  Volume 5     Z                       DVD-ROM         0 B  No Media
  Volume 6         Volume       NTFS   Partition   4095 GB  Healthy

DISKPART> 

And I can mount it from diskpart into some folder in the D: drive without a problem.

DISKPART> select volume 6

Volume 6 is the selected volume.

DISKPART> assign mount=d:\tmp

DiskPart successfully assigned the drive letter or mount point.

DISKPART>

I wants 2 c mah driv!

Similar to an issue we talked about in an early article, we have a bad case of access control being too controlling. Specifically, the mount stuff is defined in Drive_Letter:\System Volume Information\spp for each drive and those who need to see it are not. Let me show you what I mean. Here is the E: drive, which we know we can mount a fileshare inside it using the GUI.

PS C:\Users\raub.adm> cacls 'e:\System Volume Information\spp'
e:\System Volume Information\SPP AD\SOD_Domain Admins:(OI)(CI)(ID)F
                                 BUILTIN\Administrators:(OI)(CI)(ID)F
                                 NT AUTHORITY\SYSTEM:(OI)(CI)(ID)F

PS C:\Users\raub.adm>

Now here is the problematic D: drive:

PS C:\Users\raub.adm> cacls 'd:\System Volume Information\spp'
d:\System Volume Information\SPP AD\NETWORK_Domain Admins:(OI)(CI)(ID)F

PS C:\Users\raub.adm>

As you can see, the local admin entities, Administrators and the SYTEM accounts, can't access D:. So let's correct that:

PS C:\Users\raub.adm> cacls 'd:\System Volume Information\spp' /e /g system:f
processed dir: d:\System Volume Information\SPP
PS C:\Users\raub.adm> cacls 'd:\System Volume Information\spp' /e /g administrators:f
processed dir: d:\System Volume Information\SPP
PS C:\Users\raub.adm> cacls 'd:\System Volume Information\spp'
d:\System Volume Information\SPP NT AUTHORITY\SYSTEM:(OI)(CI)F
                                 BUILTIN\Administrators:(OI)(CI)F
                                 AD\NETWORK_Domain Admins:(OI)(CI)(ID)F

PS C:\Users\raub.adm>


Much better! Moral of the story, if you or your computer cannot access a drive in some way or form, do check permissions.

Tuesday, September 26, 2017

Forcing a fuse (sshfs) network fileshare to unmount in OSX

As some of you already know, I do have an old MacBook Air which I use as my main (as in the computer I sit in front of, not the computer I store data on. Laptops can be stolen, you know) machine until I find a new Linux laptop replacement. For this reason I need it to play nice with other machines, and that requires sometimes to mount a fileshare. If the other host is in the same VLAN, that is rather easy because there are ways to mount a Windows (SMB/CIFS) and even a Linux/UNIX (nfs) fileshare without breaking a sweat. But what if the machine is remote? If we can ssh into it, why not then use sshfs?

As we are aware of (since we read the link. There are a few more sshfs examples here), sshfs requires fuse. Since I am using OSX, which at the present time does not have it, I need to install. If you are curious, the one I use is FUSE for MacOS.

Mounting: business as usual

Let's say we are in the machine boris as user pickles trying to mount my home directory off desktop. We create the mountpoint (Let's use /tmp/D or ~/D so it looks more like what we would do in Linux:

boris:Documents pickles$ mkdir /tmp/D; sshfs raub@desktop.in.example.com:. /tmp/D
boris:Documents pickles$ df -h
Filesystem                     Size   Used  Avail Capacity  iused    ifree %iused  Mounted on
/dev/disk1                    112Gi   79Gi   33Gi    71% 20783599  8546848   71%   /
devfs                         364Ki  364Ki    0Bi   100%     1259        0  100%   /dev
map -hosts                      0Bi    0Bi    0Bi   100%        0        0  100%   /net
map auto_home                   0Bi    0Bi    0Bi   100%        0        0  100%   /home
raub@desktop.in.example.com:.  492Gi  389Gi  102Gi    80%   408428 32359572    1%   /private/tmp/D
boris:Documents pickles$

So far so good. To unmount it we can use diskutil, as in (Mac)

boris:Documents pickles$ diskutil umount /tmp/D
Unmount successful for /tmp/D
boris:Documents pickles$

or (Linux)

fusermount -u /tmp/D

Or go old school (both):

sudo mount /tmp/D

Since boris is a laptop, sometimes if we just let it go to sleep it will unmount it. Then, all we have to do is mount it again.

Mounting again: not so fast

Thing is, sometimes it does not work.

boris:Documents pickles$ mkdir /tmp/D; sshfs raub@desktop.in.example.com:. /tmp/D
mkdir: /tmp/D: File exists
fuse: bad mount point `/tmp/D': Input/output error
boris:Documents pickles$ 

Ok, maybe it did not automagically unmounted while laptop was off. So, let's tell it to do so:

boris:Documents pickles$ diskutil umount /tmp/D
Unmount failed for /tmp/D
boris:Documents pickles$ 

Just before you ask, sudo mount /tmp/D did not work either. What if the old sshfs processes did not cleanly closed and as a result are still lingering? To answer that we must elicit some help from one of grep's cousins, pgrep:

boris:Documents pickles$ pgrep -lf sshfs
384 sshfs raub@desktop.in.example.com:. /tmp/D
1776 sshfs raub@desktop.in.example.com:. /tmp/D
7356 sshfs user@other.in.example.com:. /tmp/D
boris:Documents pickles$

Just as we guessed, there are not only but quite a few unhappy sshfs instances. Let's see if we can kill them:

boris:Documents pickles$ kill 384 1776 7356
boris:Documents pickles$ pgrep -lf sshfs
384 sshfs raub@desktop.in.example.com:. /tmp/D
1776 sshfs raub@desktop.in.example.com:. /tmp/D
boris:Documents pickles$ kill 384
boris:Documents pickles$ pgrep -lf sshfs
384 sshfs raub@desktop.in.example.com:. /tmp/D
1776 sshfs raub@desktop.in.example.com:. /tmp/D
boris:Documents pickles$ kill 1776
boris:Documents pickles$ pgrep -lf sshfs
384 sshfs raub@desktop.in.example.com:. /tmp/D
1776 sshfs raub@desktop.in.example.com:. /tmp/D
boris:Documents pickles$
Hmmm, this is going nowhere slowly. Let's crank up a notch and force it to kill the mount.
boris:Documents pickles$ kill -9 1776
boris:Documents pickles$ pgrep -lf sshfs
384 sshfs raub@desktop.in.example.com:. /tmp/D
boris:Documents pickles$ kill -9 384
boris:Documents pickles$ pgrep -lf sshfs
\boris:Documents pickles$

Sounds like we got them all. Now, let's try and mount once more:

boris:Documents pickles$ mkdir /tmp/D; sshfs raub@desktop.in.example.com:. /tmp/D
mkdir: /tmp/D: File exists
raub@desktop.in.example.com's password:
boris:Documents pickles$

I think we have a winner!