In this post, we're going to create the skeletton of the actual container, set up the flow of our software and create an empty algorithm we'll fill with the parts needed as we move on through the tutorial.
Basis of the container
Now that the project basis are stable, the args are gathered and validated, we're going to extract the configuration into a ContainerOpts
struct and initialize a Container
struct that will have to perform the container work.
The configuration
In a new file src/config.rs
, let's define the struct for the container configuration.
use crate Errcode;
use CString;
use PathBuf;
Let's analyse what we got there: - path: The path of the binary / executable / script to execute inside the container - argv: The full arguments passed (including the path
option) into the commandline.
These are required to perform an execve syscall which we will use to contain our software in a process whose execution context is restricted. - uid: The ID of the user inside the container. An ID of0
means it's root (administrator). The user ID is visible on GNU/Linux by looking the file/etc/passwd
, who has a formatusername:x:uuid:guid:comment:homedir:shell
- mount_dir: The path of the directory we want to use as a/
root inside our container.
Configurations will be added later as we need them.
We are using CString because it'll be much easier to use to call the execve
syscall later. Also as the configuration will be shared with the child process to be created, we need to be able to clone
the struct (which contains data stored on the heap), that's why we add a derive(Clone)
attribute to the struct.
(See the chapter about ownership & data copy in the Book)
Let's create the constructor of our configuration struct
Nothing too complicated here, we just get each arg from the command String
, and creates aVec<CString>
from them, cloning the first one, and creating the struct while returning an Ok
result.
The container skeletton
Let's now create the Container struct
that will perform the main tasks ahead.
use crate Args;
use crate Errcode;
use crate ContainerOpts;
The struct Container
is defined with a unique config
field containing our configuration, and implements 3 functions: - new
function that creates the ContainerOpts
struct from the commandline arguments. - create
function that will handle the container creation process. - clean_exit
function that will be called before each exit to be sure we stay clean.
For now we let them very basic and will fill them later on.
Finally, we create a function start
that will get the args from the commandline and handle everything from the struct Container
creation to the exit.
It returns a Result
that will inform if an error happened during the process.
The ?
you see at the end of the lines is used to propagate the errors. let mut container = Container::new(args)?;
is the equivalent of:
let mut container = match new
However for this, the type in case of Err
has to be the same. That's why having a uniqueErrcode
for all our errors in the project is handy, we can basically put ?
everywhere and "cascade" any error back to the start
function which will call clean_exit
before logging the error and exit the process with an error return code thanks to the exit_with_retcode
function we defined in the part about errors handling.
Linking to the main function
One last thing, we need to call the start
function from our main
function.
In src/main.rs
, add the following at the beginning of the file:
And replace the exit_with_retcode(Ok(()))
with exit_with_retcode(container::start(args))
.
After testing, we get the following output:
[2021-10-02T14:16:41Z INFO crabcan] Args { debug: true, command: "/bin/bash", uid: 0, mount_dir: "./mountdir/" }
[2021-10-02T14:16:41Z DEBUG crabcan::container] Creation finished
[2021-10-02T14:16:41Z DEBUG crabcan::container] Finished, cleaning & exit
[2021-10-02T14:16:41Z DEBUG crabcan::container] Cleaning container
[2021-10-02T14:16:41Z 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 “step5”.
The raw patch to apply on the previous step can be found here
Checking the Linux kernel version
This step is entirely based on the <<check-linux-version>>
part of the original tutorial.
However as I work on a much newer version of the kernel, I will just check that the kernel version is at least the v4.8
one, and that the architecture is x86
.
Getting system information
As we want to start interacting with the system to gather information, we will start to use a crate that will be massively useful later, the nix crate.
Let's check our kernel version:
pub const MINIMAL_KERNEL_VERSION: f32 = 4.8;
In this code, we first get the information on the system using uname.
From these information, we get the kernel version as a f32
float using the scan_fmt crate and check if it's at least the v4.8
, then check if the machine architecture is x86_64
.
Handle errors
If the kernel version is too low or we have a wrong architecture, the function returns aErrcode::NotSupported
, with a number indicating what was not supported.
If the scan_fmt fails, we return a Errcode::ContainerError
, a new error type for the "not supposed to happen at all" kind of errors, in our container.
Let's add these new errors to the src/errors.rs
file:
Add to flow & test
As we will use a macro from the scan_fmt
crate, let's import it in our src/main.rs
:
extern crate scan_fmt;
And add the needed dependencies in the Cargo.toml
file:
[dependencies]
# ...
nix = "0.22.1"
scan_fmt = "0.2.6"
Finally, let's insert the check_linux_version
function into the flow of our start
function insrc/container.rs
:
I won't write again how errors handling are so elegant in Rust, but check out how we wired a new function into the flow without needing any additional line of code to handle its errors.
After testing that's the kind of output we get:
[2021-10-02T14:50:14Z INFO crabcan] Args { debug: true, command: "/bin/bash", uid: 0, mount_dir: "./mountdir/" }
[2021-10-02T14:50:14Z DEBUG crabcan::container] Linux release: 5.11.0-36-generic
[2021-10-02T14:50:14Z DEBUG crabcan::container] Creation finished
[2021-10-02T14:50:14Z DEBUG crabcan::container] Finished, cleaning & exit
[2021-10-02T14:50:14Z DEBUG crabcan::container] Cleaning container
[2021-10-02T14:50:14Z 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 “step6”.
The raw patch to apply on the previous step can be found here