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. Now that the project basis are stable, the args are gathered and validated, we're going to extract the configuration into a In a new file 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 We are using CString because it'll be much easier to use to call the Let's create the constructor of our configuration struct Nothing too complicated here, we just get each arg from the command Let's now create the Container The Finally, we create a function The However for this, the type in case of One last thing, we need to call the And replace the After testing, we get the following output: The code for this step is available on github litchipi/crabcan branch “step5”. This step is entirely based on the 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: In this code, we first get the information on the system using uname. If the kernel version is too low or we have a wrong architecture, the function returns a Let's add these new errors to the As we will use a macro from the And add the needed dependencies in the Finally, let's insert the After testing that's the kind of output we get: The code for this step is available on github litchipi/crabcan branch “step6”. Basis of the container
ContainerOpts
struct and initialize a Container
struct that will have to perform the container work.The configuration
src/config.rs
, let's define the struct for the container configuration.use crate Errcode;
use CString;
use PathBuf;
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 of
0
means it's root (administrator). The user ID is visible on GNU/Linux by looking the file /etc/passwd
, who has a format username: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.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)String
, and creates aVec<CString>
from them, cloning the first one, and creating the struct while returning an Ok
result.The container skeletton
struct
that will perform the main tasks ahead.use crate Args;
use crate Errcode;
use crate ContainerOpts;
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.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.?
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
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
start
function from our main
function.
In src/main.rs
, add the following at the beginning of the file:
exit_with_retcode(Ok(()))
with exit_with_retcode(container::start(args))
.[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 raw patch to apply on the previous step can be found hereChecking the Linux kernel version
<<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
pub const MINIMAL_KERNEL_VERSION: f32 = 4.8;
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
Errcode::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.src/errors.rs
file:Add to flow & test
scan_fmt
crate, let's import it in our src/main.rs
:extern crate scan_fmt;
Cargo.toml
file:[dependencies]
# ...
nix = "0.22.1"
scan_fmt = "0.2.6"
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.
[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 raw patch to apply on the previous step can be found here
Creating the skeletton
Part 3 of serie Writing a container in rust
Published:
Want us to work together ?
Hire me
Hire me