Skip to content

Commit 4b2ade5

Browse files
committed
imgstorage: Set selinux labels for imgstorage
Running some containers (e.g. mssql) requires the imgstorage labels to be identical to the /var/lib/containers/storage. So, this code recursively sets the labels for the bootc storage directory to mimic /var/lib/containers/storage. This operation is done once, then a .bootc_labeled file is created to signify the directory was labeled. This operation could be done anytime the storage is accessed, i.e. on installation, upgrade, or running a `bootc image` command. Signed-off-by: ckyrouac <ckyrouac@redhat.com>
1 parent 356b73d commit 4b2ade5

File tree

7 files changed

+131
-20
lines changed

7 files changed

+131
-20
lines changed

lib/src/boundimage.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,11 @@ pub(crate) async fn pull_images(
148148
sysroot: &Storage,
149149
bound_images: Vec<crate::boundimage::BoundImage>,
150150
) -> Result<()> {
151-
// Only do work like initializing the image storage if we have images to pull.
151+
// Always initialize the img store to ensure labels are set when upgrading
152+
let imgstore = sysroot.get_ensure_imgstore()?;
152153
if bound_images.is_empty() {
153154
return Ok(());
154155
}
155-
let imgstore = sysroot.get_ensure_imgstore()?;
156156
pull_images_impl(imgstore, bound_images).await
157157
}
158158

lib/src/imgstorage.rs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use cap_std_ext::cap_tempfile::TempDir;
2020
use cap_std_ext::cmdext::CapStdExtCommandExt;
2121
use cap_std_ext::dirext::CapStdExtDirExt;
2222
use fn_error_context::context;
23+
use ostree_ext::ostree::{self};
2324
use std::os::fd::OwnedFd;
2425
use tokio::process::Command as AsyncCommand;
2526

@@ -35,6 +36,8 @@ pub(crate) const STORAGE_ALIAS_DIR: &str = "/run/bootc/storage";
3536
/// We pass this via /proc/self/fd to the child process.
3637
const STORAGE_RUN_FD: i32 = 3;
3738

39+
const LABELED: &str = ".bootc_labeled";
40+
3841
/// The path to the image storage, relative to the bootc root directory.
3942
pub(crate) const SUBPATH: &str = "storage";
4043
/// The path to the "runroot" with transient runtime state; this is
@@ -161,24 +164,68 @@ impl Storage {
161164
Ok(())
162165
}
163166

167+
#[context("Labeling imgstorage dirs")]
168+
fn label_dirs(root: &Dir, sepolicy: Option<&ostree::SePolicy>) -> Result<()> {
169+
if root.try_exists(LABELED)? {
170+
return Ok(());
171+
}
172+
let Some(sepolicy) = sepolicy else {
173+
return Ok(());
174+
};
175+
176+
// recursively set the labels because they were previously set to usr_t,
177+
// and there is no policy defined to set them to the c/storage labels
178+
crate::lsm::ensure_dir_labeled_recurse_policy(
179+
&root,
180+
".",
181+
Some(Utf8Path::new("/var/lib/containers/storage")),
182+
0o755.into(),
183+
Some(sepolicy),
184+
)
185+
.context("labeling storage root")?;
186+
187+
let paths = ["overlay", "overlay-images", "overlay-layers", "volumes"];
188+
for p in paths {
189+
let full_label_path = format!("/var/lib/containers/storage/{}", p);
190+
crate::lsm::ensure_dir_labeled_recurse_policy(
191+
&root,
192+
p,
193+
Some(Utf8Path::new(full_label_path.as_str())),
194+
0o755.into(),
195+
Some(sepolicy),
196+
)
197+
.context(format!("labeling storage subpath: {}", p))?;
198+
}
199+
200+
root.create(LABELED)?;
201+
202+
Ok(())
203+
}
204+
164205
#[context("Creating imgstorage")]
165-
pub(crate) fn create(sysroot: &Dir, run: &Dir) -> Result<Self> {
206+
pub(crate) fn create(
207+
sysroot: &Dir,
208+
run: &Dir,
209+
sepolicy: Option<&ostree::SePolicy>,
210+
) -> Result<Self> {
166211
Self::init_globals()?;
167212
let subpath = &Self::subpath();
168213

169214
// SAFETY: We know there's a parent
170215
let parent = subpath.parent().unwrap();
216+
let tmp = format!("{subpath}.tmp");
171217
if !sysroot
172218
.try_exists(subpath)
173219
.with_context(|| format!("Querying {subpath}"))?
174220
{
175-
let tmp = format!("{subpath}.tmp");
176221
sysroot.remove_all_optional(&tmp).context("Removing tmp")?;
177222
sysroot
178223
.create_dir_all(parent)
179224
.with_context(|| format!("Creating {parent}"))?;
180225
sysroot.create_dir_all(&tmp).context("Creating tmpdir")?;
181226
let storage_root = sysroot.open_dir(&tmp).context("Open tmp")?;
227+
Self::label_dirs(&storage_root, sepolicy)?;
228+
182229
// There's no explicit API to initialize a containers-storage:
183230
// root, simply passing a path will attempt to auto-create it.
184231
// We run "podman images" in the new root.
@@ -192,7 +239,12 @@ impl Storage {
192239
.rename(&tmp, sysroot, subpath)
193240
.context("Renaming tmpdir")?;
194241
tracing::debug!("Created image store");
242+
} else {
243+
// the storage already exists, make sure it has selinux labels
244+
let storage_root = sysroot.open_dir(subpath).context("opening storage dir")?;
245+
Self::label_dirs(&storage_root, sepolicy)?;
195246
}
247+
196248
Self::open(sysroot, run)
197249
}
198250

lib/src/install.rs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,10 @@ pub(crate) fn print_configuration() -> Result<()> {
605605
}
606606

607607
#[context("Creating ostree deployment")]
608-
async fn initialize_ostree_root(state: &State, root_setup: &RootSetup) -> Result<(Storage, bool)> {
608+
async fn initialize_ostree_root(
609+
state: &State,
610+
root_setup: &RootSetup,
611+
) -> Result<(Storage, bool, crate::imgstorage::Storage)> {
609612
let sepolicy = state.load_policy()?;
610613
let sepolicy = sepolicy.as_ref();
611614
// Load a fd for the mounted target physical root
@@ -671,14 +674,9 @@ async fn initialize_ostree_root(state: &State, root_setup: &RootSetup) -> Result
671674

672675
state.tempdir.create_dir("temp-run")?;
673676
let temp_run = state.tempdir.open_dir("temp-run")?;
674-
sysroot_dir
675-
.create_dir_all(Utf8Path::new(crate::imgstorage::SUBPATH).parent().unwrap())
676-
.context("creating bootc dir")?;
677-
let imgstore = crate::imgstorage::Storage::create(&sysroot_dir, &temp_run)?;
678-
// And drop it again - we'll reopen it after this
679-
drop(imgstore);
680677

681678
// Bootstrap the initial labeling of the /ostree directory as usr_t
679+
// and create the imgstorage with the same labels as /var/lib/containers
682680
if let Some(policy) = sepolicy {
683681
let ostree_dir = rootfs_dir.open_dir("ostree")?;
684682
crate::lsm::ensure_dir_labeled(
@@ -690,9 +688,11 @@ async fn initialize_ostree_root(state: &State, root_setup: &RootSetup) -> Result
690688
)?;
691689
}
692690

691+
let imgstore = crate::imgstorage::Storage::create(&sysroot_dir, &temp_run, sepolicy)?;
692+
693693
sysroot.load(cancellable)?;
694694
let sysroot = SysrootLock::new_from_sysroot(&sysroot).await?;
695-
Ok((Storage::new(sysroot, &temp_run)?, has_ostree))
695+
Ok((Storage::new(sysroot, &temp_run)?, has_ostree, imgstore))
696696
}
697697

698698
#[context("Creating ostree deployment")]
@@ -1322,6 +1322,7 @@ async fn install_with_sysroot(
13221322
boot_uuid: &str,
13231323
bound_images: BoundImages,
13241324
has_ostree: bool,
1325+
imgstore: &crate::imgstorage::Storage,
13251326
) -> Result<()> {
13261327
// And actually set up the container in that root, returning a deployment and
13271328
// the aleph state (see below).
@@ -1349,10 +1350,6 @@ async fn install_with_sysroot(
13491350

13501351
tracing::debug!("Perfoming post-deployment operations");
13511352

1352-
// Note that we *always* initialize this container storage, even if there are no bound images
1353-
// today.
1354-
let imgstore = sysroot.get_ensure_imgstore()?;
1355-
13561353
match bound_images {
13571354
BoundImages::Skip => {}
13581355
BoundImages::Resolved(resolved_bound_images) => {
@@ -1438,14 +1435,16 @@ async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Re
14381435

14391436
// Initialize the ostree sysroot (repo, stateroot, etc.)
14401437
{
1441-
let (sysroot, has_ostree) = initialize_ostree_root(state, rootfs).await?;
1438+
let (sysroot, has_ostree, imgstore) = initialize_ostree_root(state, rootfs).await?;
1439+
14421440
install_with_sysroot(
14431441
state,
14441442
rootfs,
14451443
&sysroot,
14461444
&boot_uuid,
14471445
bound_images,
14481446
has_ostree,
1447+
&imgstore,
14491448
)
14501449
.await?;
14511450
// We must drop the sysroot here in order to close any open file

lib/src/install/completion.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ use fn_error_context::context;
1313
use ostree_ext::{gio, ostree};
1414
use rustix::fs::Mode;
1515
use rustix::fs::OFlags;
16+
use std::os::fd::AsRawFd;
17+
18+
use crate::utils::deployment_fd;
1619

1720
use super::config;
1821

@@ -289,9 +292,15 @@ pub(crate) async fn impl_completion(
289292
// ostree-ext doesn't do logically bound images
290293
let bound_images = crate::boundimage::query_bound_images_for_deployment(sysroot, deployment)?;
291294
if !bound_images.is_empty() {
295+
// load the selinux policy from the target ostree deployment
296+
let deployment_fd = deployment_fd(sysroot, deployment)?;
297+
let sepolicy =
298+
&ostree::SePolicy::new_at(deployment_fd.as_raw_fd(), gio::Cancellable::NONE)?;
299+
292300
// When we're run through ostree, we only lazily initialize the podman storage to avoid
293301
// having a hard dependency on it.
294-
let imgstorage = &crate::imgstorage::Storage::create(&sysroot_dir, &rundir)?;
302+
let imgstorage =
303+
&crate::imgstorage::Storage::create(&sysroot_dir, &rundir, Some(sepolicy))?;
295304
crate::boundimage::pull_images_impl(imgstorage, bound_images)
296305
.await
297306
.context("pulling bound images")?;

lib/src/lsm.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,36 @@ pub(crate) fn ensure_labeled(
259259
Ok(r)
260260
}
261261

262+
pub(crate) fn ensure_dir_labeled_recurse_policy(
263+
root: &Dir,
264+
destname: impl AsRef<Utf8Path>,
265+
as_path: Option<&Utf8Path>,
266+
mode: rustix::fs::Mode,
267+
policy: Option<&ostree::SePolicy>,
268+
) -> Result<()> {
269+
ensure_dir_labeled(root, &destname, as_path, mode, policy)?;
270+
let mut dest_path = destname.as_ref().to_path_buf();
271+
272+
for ent in root.read_dir(destname.as_ref())? {
273+
let ent = ent?;
274+
let metadata = ent.metadata()?;
275+
let name = ent.file_name();
276+
let name = name
277+
.to_str()
278+
.ok_or_else(|| anyhow::anyhow!("Invalid non-UTF-8 filename: {name:?}"))?;
279+
dest_path.push(name);
280+
281+
if metadata.is_dir() {
282+
ensure_dir_labeled_recurse_policy(root, &dest_path, as_path, mode, policy)?;
283+
} else {
284+
ensure_dir_labeled(root, &dest_path, as_path, mode, policy)?
285+
}
286+
dest_path.pop();
287+
}
288+
289+
Ok(())
290+
}
291+
262292
/// A wrapper for creating a directory, also optionally setting a SELinux label.
263293
/// The provided `skip` parameter is a device/inode that we will ignore (and not traverse).
264294
pub(crate) fn ensure_dir_labeled_recurse(

lib/src/store/mod.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ use cap_std_ext::cap_std::fs::Dir;
77
use cap_std_ext::dirext::CapStdExtDirExt;
88
use clap::ValueEnum;
99
use fn_error_context::context;
10+
use std::os::fd::AsRawFd;
1011

1112
use ostree_ext::container::OstreeImageReference;
1213
use ostree_ext::keyfileext::KeyFileExt;
13-
use ostree_ext::ostree;
1414
use ostree_ext::sysroot::SysrootLock;
15+
use ostree_ext::{gio, ostree};
1516

1617
use crate::spec::ImageStatus;
18+
use crate::utils::deployment_fd;
1719

1820
mod ostree_container;
1921

@@ -85,7 +87,18 @@ impl Storage {
8587
return Ok(imgstore);
8688
}
8789
let sysroot_dir = crate::utils::sysroot_dir(&self.sysroot)?;
88-
let imgstore = crate::imgstorage::Storage::create(&sysroot_dir, &self.run)?;
90+
91+
if self.sysroot.booted_deployment().is_none() {
92+
anyhow::bail!("Not a bootc system (this shouldn't be possible)");
93+
}
94+
95+
// load the sepolicy from the booted ostree deployment so the imgstorage can be
96+
// properly labeled with /var/lib/container/storage labels
97+
let dep = self.sysroot.booted_deployment().unwrap();
98+
let dep_fs = deployment_fd(&self.sysroot, &dep)?;
99+
let sepolicy = &ostree::SePolicy::new_at(dep_fs.as_raw_fd(), gio::Cancellable::NONE)?;
100+
101+
let imgstore = crate::imgstorage::Storage::create(&sysroot_dir, &self.run, Some(sepolicy))?;
89102
Ok(self.imgstore.get_or_init(|| imgstore))
90103
}
91104

tests/booted/test-logically-bound-install.nu

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,15 @@ def test_bootc_image_list [] {
3535
validate_images $images
3636
}
3737

38+
def test_storage_labels [] {
39+
let root_labeled = getfattr -n security.selinux /var/lib/containers/storage | grep container_var_lib_t | complete
40+
assert equal $root_labeled.exit_code 0
41+
let overlay_labeled = getfattr -n security.selinux /usr/lib/bootc/storage/overlay | grep container_ro_file_t | complete
42+
assert equal $overlay_labeled.exit_code 0
43+
}
44+
3845
test_logically_bound_images_in_storage
3946
test_bootc_image_list
47+
test_storage_labels
4048

4149
tap ok

0 commit comments

Comments
 (0)