Skip to content

Commit

Permalink
Merge pull request #47 from OSInside/allow_podman_mount_in_rootless_mode
Browse files Browse the repository at this point in the history
Allow to mount podman storage in rootless mode
  • Loading branch information
schaefi authored Nov 5, 2024
2 parents 0c7adea + 3657fd7 commit 1db28a8
Show file tree
Hide file tree
Showing 14 changed files with 342 additions and 59 deletions.
3 changes: 3 additions & 0 deletions common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ log = { version = "0.4" }
thiserror = { version = "1.0" }
serde_yaml = { version = "0.9" }
lazy_static = { version = "1.4" }
users = { version = "0.11" }
rust-ini = { version = "0.21" }
tempfile = { version = "3.4" }

[features]
json = ["serde_json"]
119 changes: 119 additions & 0 deletions common/src/container.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//
// Copyright (c) 2023 SUSE Software Solutions Germany GmbH
//
// This file is part of flake-pilot
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
extern crate ini;

use crate::flakelog::FlakeLog;
use crate::error::FlakeError;
use crate::user::User;
use crate::command::CommandExtTrait;
use tempfile::NamedTempFile;
use std::path::Path;
use std::fs;
use std::os::unix::fs::MetadataExt;
use std::env;
use ini::Ini;

#[derive(Debug, Default, Clone, Copy)]
pub struct Container {
}

impl Container {
pub fn podman_write_custom_storage_config(
mut storage_conf: &NamedTempFile
) -> Result<(), FlakeError> {
/*!
Create storage conf to point root to the user
storage data such that mounting becomes possible
!*/
let mut storage = Ini::new();
storage.with_section(Some("storage"))
.set("driver", "\"overlay\"")
.set(
"graphroot",
format!("\"{}/.local/share/containers/storage\"",
env::var("HOME").unwrap()
)
);
storage.write_to(&mut storage_conf)?;
Ok(())
}

pub fn podman_fix_storage_permissions(
runas: &str
) -> Result<(), FlakeError> {
/*!
Fix user storage permissions
!*/
let user = User::from(runas);
let user_name = user.get_name();
let user_id = user.get_user_id();
let user_group = user.get_group_name();
let root = User::from("root");
let mut fix_run = root.run("chown");
fix_run.arg("-R")
.arg(format!("{}:{}", user_name, user_group))
.arg(format!("/run/user/{}/containers", user_id));
FlakeLog::debug(&format!("{:?}", fix_run.get_args()));
fix_run.perform()?;

let paths;
let storage_dir = format!(
"/home/{}/.local/share/containers/storage/overlay", user_name
);
match fs::read_dir(storage_dir.clone()) {
Ok(result) => { paths = result },
Err(error) => {
return Err(FlakeError::IOError {
kind: format!("{:?}", error.kind()),
message: format!("fs::read_dir failed on {}: {}",
storage_dir, error
)
})
}
};
for path in paths {
let file_path = path.unwrap().path();
let work_path = format!("{}/work/work", file_path.display());
let meta = fs::metadata(&file_path)?;
if Path::new(&work_path).exists() {
let work_meta = fs::metadata(&work_path)?;
if work_meta.uid() == 0 {
let mut fix_storage = root.run("chown");
fix_storage.arg("-R")
.arg(format!("{}:{}", user_name, user_group))
.arg(&file_path);
FlakeLog::debug(&format!("{:?}", fix_storage.get_args()));
fix_storage.perform()?;
}
} else if meta.uid() == 0 {
let mut fix_storage = root.run("chown");
fix_storage.arg(format!("{}:{}", user_name, user_group))
.arg(&file_path);
FlakeLog::debug(&format!("{:?}", fix_storage.get_args()));
fix_storage.perform()?;
}
}
Ok(())
}
}
1 change: 1 addition & 0 deletions common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ pub mod config;
pub mod flakelog;
pub mod defaults;
pub mod io;
pub mod container;
17 changes: 17 additions & 0 deletions common/src/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use std::{process::Command, ffi::OsStr};
use serde::{Serialize, Deserialize};
use crate::command::{CommandExtTrait, CommandError};
use users::{get_current_uid, get_current_groupname};

#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
pub struct User<'a> {
Expand All @@ -40,6 +41,22 @@ impl<'a> From<&'a str> for User<'a> {
impl<'a> User<'a> {
pub const ROOT: User<'static> = User { name: Some("root")};

pub fn get_user_id(&self) -> String {
get_current_uid().to_string()
}

pub fn get_group_name(&self) -> String {
get_current_groupname().unwrap().into_string().unwrap()
}

pub fn get_name(&self) -> String {
let mut user = String::new();
if let Some(name) = self.name {
user.push_str(name)
}
user
}

pub fn run<S: AsRef<OsStr>>(&self, command: S) -> Command {
let mut c = Command::new("sudo");
if let Some(name) = self.name {
Expand Down
6 changes: 6 additions & 0 deletions firecracker-pilot/src/firecracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,12 @@ pub fn create(program_name: &String) -> Result<(String, String), FlakeError> {
)
})
}
// provisioning needs root permissions for mount
// make sure we have them for this session
let root_user = User::from("root");
let mut root = root_user.run("true");
root.status()?;

// setup VM ID file name
let vm_id_file_path = get_meta_file_name(
program_name, &get_firecracker_ids_dir(), "vmid"
Expand Down
1 change: 1 addition & 0 deletions flake-ctl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ indicatif = { version = "0.15" }
tokio = { version = "1.32", features = ["full"] }
tempfile = { version = "3.4" }
flakes = { version = "3.0.13", path = "../common" }
nix = { version = "0.29", features = ["user"] }
4 changes: 2 additions & 2 deletions flake-ctl/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ pub fn create_container_config(
/*!
Create app configuration for the container engine.
Create an app configuration file as FLAKE_DIR/app.yaml
Create an app configuration file as get_flakes_dir()/app.yaml
containing the required information to launch the
application inside of the container engine.
!*/
Expand Down Expand Up @@ -165,7 +165,7 @@ pub fn create_vm_config(
/*!
Create app configuration for the firecracker engine.
Create an app configuration file as FLAKE_DIR/app.yaml
Create an app configuration file as get_flakes_dir()/app.yaml
containing the required information to launch the
application inside of the firecracker engine.
!*/
Expand Down
2 changes: 0 additions & 2 deletions flake-ctl/src/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
pub const FLAKE_DIR: &str =
"/usr/share/flakes";
pub const PODMAN_PILOT: &str =
"/usr/bin/podman-pilot";
pub const FIRECRACKER_PILOT: &str =
Expand Down
3 changes: 2 additions & 1 deletion flake-ctl/src/firecracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
use flakes::config::get_flakes_dir;
use std::ffi::OsStr;
use std::os::unix::fs::PermissionsExt;
use std::process::Command;
Expand Down Expand Up @@ -486,7 +487,7 @@ pub fn purge_vm(vm: &str) {
!*/
for app_name in app::app_names() {
let config_file = format!(
"{}/{}.yaml", defaults::FLAKE_DIR, app_name
"{}/{}.yaml", get_flakes_dir(), app_name
);
match app_config::AppConfig::init_from_file(Path::new(&config_file)) {
Ok(mut app_conf) => {
Expand Down
80 changes: 64 additions & 16 deletions flake-ctl/src/podman.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ use std::path::Path;
use std::process::{Command, Stdio};
use crate::defaults;
use crate::{app, app_config};
use nix::unistd::{Uid, User};
use tempfile::NamedTempFile;
use flakes::container::Container;
use flakes::config::get_flakes_dir;

pub fn pull(uri: &String) -> i32 {
/*!
Expand Down Expand Up @@ -61,7 +65,6 @@ pub fn pull(uri: &String) -> i32 {
status_code
}


pub fn load(oci: &String) -> i32 {
/*!
Call podman load with the provided oci tar file
Expand Down Expand Up @@ -117,14 +120,37 @@ pub fn mount_container(container_name: &str) -> String {
Mount container and return mount point,
or an empty string in the error case
!*/
match Command::new(defaults::PODMAN_PATH)
.arg("image")
.arg("mount")
.arg(container_name)
.output()
{
let mut runs_as_root = false;
let storage_conf = NamedTempFile::new().unwrap();
let uid = Uid::effective();
if uid.is_root() {
runs_as_root = true
}
let mut call = Command::new("sudo");
if !runs_as_root {
let _ = Container::podman_write_custom_storage_config(&storage_conf);
call.arg("bash").arg("-c")
.arg(format!(
"export CONTAINERS_STORAGE_CONF={}; {} image mount {}",
storage_conf.path().display(),
defaults::PODMAN_PATH,
container_name
)
);
} else {
call.arg(defaults::PODMAN_PATH)
.arg("image")
.arg("mount")
.arg(container_name);
}
match call.output() {
Ok(output) => {
if output.status.success() {
if !runs_as_root {
let _ = Container::podman_fix_storage_permissions(
&User::from_uid(uid).unwrap().unwrap().name
);
}
return String::from_utf8_lossy(&output.stdout)
.strip_suffix('\n').unwrap().to_string()
}
Expand All @@ -144,16 +170,38 @@ pub fn umount_container(container_name: &str) -> i32 {
/*!
Umount container image
!*/
let mut runs_as_root = false;
let mut status_code = 255;
match Command::new(defaults::PODMAN_PATH)
.stderr(Stdio::null())
.stdout(Stdio::null())
.arg("image")
.arg("umount")
.arg(container_name)
.status()
{
let storage_conf = NamedTempFile::new().unwrap();
let uid = Uid::effective();
if uid.is_root() {
runs_as_root = true
}
let mut call = Command::new("sudo");
if !runs_as_root {
let _ = Container::podman_write_custom_storage_config(&storage_conf);
call.stderr(Stdio::null()).stdout(Stdio::null())
.arg("bash").arg("-c")
.arg(format!(
"export CONTAINERS_STORAGE_CONF={}; {} image umount {}",
storage_conf.path().display(),
defaults::PODMAN_PATH,
container_name
)
);
} else {
call.arg(defaults::PODMAN_PATH)
.arg("image")
.arg("umount")
.arg(container_name);
}
match call.status() {
Ok(status) => {
if !runs_as_root {
let _ = Container::podman_fix_storage_permissions(
&User::from_uid(uid).unwrap().unwrap().name
);
}
status_code = status.code().unwrap();
},
Err(error) => {
Expand All @@ -172,7 +220,7 @@ pub fn purge_container(container: &str) {
!*/
for app_name in app::app_names() {
let config_file = format!(
"{}/{}.yaml", defaults::FLAKE_DIR, app_name
"{}/{}.yaml", get_flakes_dir(), app_name
);
match app_config::AppConfig::init_from_file(Path::new(&config_file)) {
Ok(mut app_conf) => {
Expand Down
1 change: 1 addition & 0 deletions podman-pilot/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ serde = { version = "1.0", features = ["derive"]}
serde_yaml = { version = "0.9" }
regex = { version = "1.9" }
flakes = { version = "3.0.13", path = "../common" }
rust-ini = { version = "0.21" }
1 change: 1 addition & 0 deletions podman-pilot/src/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@
pub const CONTAINER_DIR: &str = "/var/lib/containers";
pub const GC_THRESHOLD: i32 = 20;
pub const HOST_DEPENDENCIES: &str = "removed";
pub const PODMAN_PATH:&str = "/usr/bin/podman";
2 changes: 1 addition & 1 deletion podman-pilot/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ fn run() -> Result<(), FlakeError> {

fn setup_logger() {
let env = Env::default()
.filter_or("MY_LOG_LEVEL", "trace")
.filter_or("MY_LOG_LEVEL", "debug")
.write_style_or("MY_LOG_STYLE", "always");

env_logger::init_from_env(env);
Expand Down
Loading

0 comments on commit 1db28a8

Please sign in to comment.