Setting the container hostname
A hostname is what identifies our machine compared to every other living on the same network.
It is used by many different networking software, for example avahi
is a software that streams our hostname in the local network, allowing a command ssh crab@192.168.0.42
to becomessh crab@crabcan.local
, the website http://localhost:80
to http://crabcan.local
, etc ...
Check the official website of avahi for more information
In order to differentiate the operations performed by our software contained from the one performed by the host system, we will modify its hostname.
Generate a hostname
First of all, create a new file called src/hostname.rs
, in which we will write any code related to the hostname.
Inside, set two arrays of pre-defined names and adjectives that we'll use together to generate a stupid random hostname.
const HOSTNAME_NAMES: = ;
const HOSTNAME_ADJ: = ;
We then generate a string using some randomness:
use Rng;
use SliceRandom;
We obtain a hostname in the form square-moon-64, big-pinguin-2, etc ...
As we used a new Errcode::RngError
to handle errors linked to the randomness functions, we add this variant to our Errcode
enum in src/errors.rs
, along with another one that we'll use later, the Errcode::HostnameError(u8)
variant:
Also, we use the rand
crate to have randomness in our hostname generation, so we have to add it to the dependencies of Cargo.toml
:
[dependencies]
# ...
rand = "0.8.4"
Adding to the configuration of the container
Now that we have a way to generate a String
containing our "random" hostname, we can use it to set our container configuration in src/config.rs
:
use crate generate_hostname;
// ...
And finally, we can create in src/hostname.rs
the function that will modify the actual hostname of our host namespace with the new one, using the sethostname
syscall:
use crate Errcode;
use sethostname;
Check the linux manual for more information on the sethostname
syscall
Applying the configuration to the child process
For all the configuration we will apply to the child process, let's create a wrapping functions setting everything, and add the set_container_hostname
function call inside it, in src/child.rs
:
use crate set_container_hostname;
And we then simply call the configuration function at the beginning of our child process:
Note that we cannot "recover" from any error happening in our child process, so we simply end it with a retcode = -1
along with a nice error message in case a problem occurs.
The final thing to do here is adding to src/main.rs
the hostname
module we just created:
// ...
Testing
When testing, we can see the hostname we generated appear:
[2021-11-15T09:07:38Z INFO crabcan] Args { debug: true, command: "/bin/bash", uid: 0, mount_dir: "./mountdir/" }
[2021-11-15T09:07:38Z DEBUG crabcan::container] Linux release: 5.13.0-21-generic
[2021-11-15T09:07:38Z DEBUG crabcan::container] Container sockets: (3, 4)
[2021-11-15T09:07:38Z DEBUG crabcan::container] Creation finished
[2021-11-15T09:07:38Z DEBUG crabcan::container] Container child PID: Some(Pid(26003))
[2021-11-15T09:07:38Z DEBUG crabcan::container] Waiting for child (pid 26003) to finish
[2021-11-15T09:07:38Z DEBUG crabcan::hostname] Container hostname is now weird-moon-191
[2021-11-15T09:07:38Z INFO crabcan::child] Container set up successfully
[2021-11-15T09:07:38Z INFO crabcan::child] Starting container with command /bin/bash and args ["/bin/bash"]
[2021-11-15T09:07:38Z DEBUG crabcan::container] Finished, cleaning & exit
[2021-11-15T09:07:38Z DEBUG crabcan::container] Cleaning container
[2021-11-15T09:07:38Z DEBUG crabcan::errors] Exit without any error, returning 0
And running it several times outputs different funny names :D
[2021-11-15T09:08:33Z DEBUG crabcan::hostname] Container hostname is now round-cat-221
[2021-11-15T09:08:48Z DEBUG crabcan::hostname] Container hostname is now silent-man-45
[2021-11-15T09:09:01Z DEBUG crabcan::hostname] Container hostname is now soft-cat-149
Patch for this step
The code for this step is available on github litchipi/crabcan branch “step9”.
The raw patch to apply on the previous step can be found here
Modifying the container mount point
The mount point is a directory in our system that will be the root, the /
of our container. A user can pass to the arguments a directory that will be used as the root of the container.
The process will be done as followed:
- Mount the system root
/
inside the container - Create a new temporary directory
/tmp/crabcan.<random_string>
- Mount the user-given directory to the temporary directory
- Perform a root pivot over the two mounted directories
- Unmount and delete un-necessary directories
Keep in mind that everything we mount / unmount inside the container are isolated from the rest of the system by the mount namespace.
In practise, this isolation keeps separated versions of /proc/<pid>/mountinfo
, /proc/<pid>/mountstats
and /proc/<pid>/mounts/
, that describes what is mounted where, how, etc ...
>See proc(5) linux manual or mount_namespace linux manual for more precisions on this.
Preparing the implementation
As we will create a src/mounts.rs
file containing a function setmountpoint
, let's create everything right now so we can focus on our directories later on.
In src/child.rs
, let's write our setmountpoint
function as part of the container configuration process:
use crate setmountpoint;
Then in src/container.rs
, we add a new element in the clean_exit
function:
use crate clean_mounts;
We then add a new error variant in our Errcode
enum in src/errors.rs
:
Finally, we use the src/mounts.rs
file as a module in our project. In src/main.rs
:
// ...
Remounting the root /
privately
Now to the real meat ! Let's create the src/mounts.rs
file and add the following:
use crate Errcode;
use PathBuf;
We want to remount the root /
of our filesystem with the MS_PRIVATE
flag which will prevent any mount operation to be propagated.
See this LWN article for more explanations on what the
MS_PRIVATE
flag is about,
and this other LWN article for an example.
To do this, we will create the mount_directory
function that is essentially a wrapper around the mount
syscall provided by the nix
crate.
use ;
And call it inside our setmountpoint
function:
Mount the new root
Now let's mount the directory provided by the user so we can pivot root later. I won't go into deep details of every line of code as this is simply calling library functions.
First, let's create a random_string
function that returns, well, a random string.
// Taken from https://rust-lang-nursery.github.io/rust-cookbook/algorithms/randomness.html
use Rng;
That will allow us to generate a random directory name easily.
Then, after we have our random directory name, let's create that directory:
use create_dir_all;
And finally let's tie everything together in our setmountpoint
function:
We mounted our user-provided mount_dir
to the mountpoint /tmp/crabcan.<random_letters>
, this will allow us to pivot root later and use the mount_dir
as if it was the real /
root of the system.
Pivot the root
Now to the real magic trick ! We set /tmp/crabcan.<random_letters>
as our new /
root filesystem, and we will move the old /
root into a new dir /tmp/crabcan.<random_letters>/oldroot.<random_letters>
:
Example:
Outside the container Inside the container
~/container_dir == mount ==> /tmp/crabcan.12345 == pivot ==> /
/ == pivot ==> /oldroot.54321
See the linux manual for a detailed explanation of this process
This is how we can do it in the code:
use pivot_root;
Unmounting the old root
As we want to achieve isolation with the host system, the "old root" has to be unmounted so the contained application cannot access to the whole filesystem.
To do this, we create the unmount_path
and delete_dir
functions:
use ;
use remove_dir;
And we simply call them at the end of setmountpoint
function:
use chdir;
Note: We umount and delete the/oldroot.<random_letters>
directory as it was located inside the/tmp/crabcan.<random_letters>
directory which became our new/
.
The empty cleaning function
You certainly noticed that the clean_mounts
function is totally useless right now. The problem is that the parent container doesn't have any clue of where the user-provided directory got mounted (as it's a randomly generated filename).
The only real problem it causes right now is that all the /tmp/crabcan.<random_letters>
directories created still exist after the execution, even if they are empty and unmounted after the contained process exits.
For the sake of simplicity (or laziness), I let it like this but kept the placeholder for a cleaning function if it becomes necessary one day.
Testing
When testing, we can see the new root being located at /tmp/crabcan.<random_letters>
.
[2022-01-04T06:50:25Z INFO crabcan] Args { debug: true, command: "/bin/bash", uid: 0, mount_dir: "./mountdir/" }
[2022-01-04T06:50:25Z DEBUG crabcan::container] Linux release: 5.13.0-22-generic
[2022-01-04T06:50:25Z DEBUG crabcan::container] Container sockets: (3, 4)
[2022-01-04T06:50:25Z DEBUG crabcan::container] Creation finished
[2022-01-04T06:50:25Z DEBUG crabcan::container] Container child PID: Some(Pid(324564))
[2022-01-04T06:50:25Z DEBUG crabcan::container] Waiting for child (pid 324564) to finish
[2022-01-04T06:50:25Z DEBUG crabcan::hostname] Container hostname is now blue-man-109
[2022-01-04T06:50:25Z DEBUG crabcan::mounts] Setting mount points ...
[2022-01-04T06:50:25Z DEBUG crabcan::mounts] Mounting temp directory /tmp/crabcan.wYGDJtGIKxZ4
[2022-01-04T06:50:25Z DEBUG crabcan::mounts] Pivoting root
[2022-01-04T06:50:25Z DEBUG crabcan::mounts] Unmounting old root
[2022-01-04T06:50:25Z INFO crabcan::child] Container set up successfully
[2022-01-04T06:50:25Z INFO crabcan::child] Starting container with command /bin/bash and args ["/bin/bash"]
[2022-01-04T06:50:25Z DEBUG crabcan::container] Finished, cleaning & exit
[2022-01-04T06:50:25Z DEBUG crabcan::container] Cleaning container
[2022-01-04T06:50:25Z DEBUG crabcan::errors] Exit without any error, returning 0
Patch for this step
The code for this step is available on github litchipi/crabcan branch “step10”.
The raw patch to apply on the previous step can be found here