Skip to content

Commit

Permalink
add /etc/os-release to images by default
Browse files Browse the repository at this point in the history
  • Loading branch information
tofay committed Apr 22, 2024
1 parent 98916b6 commit a2e8f3d
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 12 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
tests/**/out
out/
vendor/
tests/**/rpmoci.lock
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ id = "foo"
Whether or not documentation files are included in the produced containers can be specified via the `content.docs` boolean field.
By default documentation files are not included, optimizing for image size.

#### /etc/os-release

Whether `/etc/os-release` is automatically included as a dependency during resolution, hence installed in the produced image, can be specified via the `content.os_release` boolean field.
This enables SBOM and vulnerability scanning tools to better determine the provenance of packages within the image.
By default this field is enabled.

*The /etc/os-release file can also be included by adding the distro's `<distro>-release` package to the packages array: this field exists to ensure the /etc/os-release file is included by default.*

### Image building

Running `rpmoci build --image foo --tag bar` will build a container image in OCI format.
Expand Down Expand Up @@ -243,8 +251,6 @@ $ rpmoci build --image foo --tag bar --vendor-dir vendor
### SBOM support
rpmoci doesn't have native SBOM support, but because it just uses standard OS package functionality SBOM generators like trivy and syft can be used to generate SBOMs for the produced images.

*For these tools to detect the Linux distribution correctly you may need to install the `<distro>-release` package in the image.*

## Developing

rpmoci is written in Rust and currently resolves RPMs using DNF via an embedded Python module.
Expand Down
12 changes: 11 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,26 @@ pub(crate) struct PackageConfig {
pub(crate) packages: Vec<String>,
#[serde(default)]
pub(crate) gpgkeys: Vec<Url>,
/// Whether to install documentation files
/// Whether to install documentation files.
/// Defaults to false, to produce smaller container images.
#[serde(default = "docs_default")]
pub(crate) docs: bool,
/// Whether to include /etc/os-release as a dependency.
/// Defaults to true, so that scanning tools can detect
/// the distro of images produced by rpmoci without users
/// needing to add the <distro>-release package.
#[serde(default = "os_release_default")]
pub(crate) os_release: bool,
}

fn docs_default() -> bool {
false
}

fn os_release_default() -> bool {
true
}

/// Configuration file for rpmoci
#[derive(Debug, Serialize, Default, Deserialize, Clone)]
#[serde(deny_unknown_fields)]
Expand Down
26 changes: 18 additions & 8 deletions src/lockfile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,20 +78,20 @@ struct RepoKeyInfo {

/// A resolved package
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, PartialOrd, Eq, Ord)]
struct Package {
pub struct Package {
/// The package name
name: String,
pub name: String,
/// The package epoch-version-release
evr: String,
pub evr: String,
/// The package checksum
checksum: Checksum,
pub checksum: Checksum,
/// The id of the package's repository
repoid: String,
pub repoid: String,
}

/// Checksum of RPM package
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, PartialOrd, Eq, Ord)]
struct Checksum {
pub struct Checksum {
/// The algorithm of the checksum
algorithm: Algorithm,
/// The checksum value
Expand All @@ -101,11 +101,16 @@ struct Checksum {
/// Algorithms supported by RPM for checksums
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, PartialOrd, Eq, Ord)]
#[serde(rename_all = "lowercase")]
enum Algorithm {
MD5, //Devskim: ignore DS126858
pub enum Algorithm {
/// The MD5 algorithm
MD5, //Devskim: ignore DS126858
/// The SHA1 algorithm
SHA1, //Devskim: ignore DS126858
/// The SHA256 algorithm
SHA256,
/// The SHA384 algorithm
SHA384,
/// The SHA512 algorithm
SHA512,
}

Expand Down Expand Up @@ -179,4 +184,9 @@ impl Lockfile {

Ok(())
}

/// Returns an iterator over the packages in the Lockfile
pub fn iter_packages(&self) -> impl Iterator<Item = &Package> {
self.packages.iter()
}
}
10 changes: 9 additions & 1 deletion src/lockfile/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,22 @@ use crate::config::Repository;
impl Lockfile {
/// Perform dependency resolution on the given package specs
pub(crate) fn resolve(
pkg_specs: Vec<String>,
mut pkg_specs: Vec<String>,
repositories: &[Repository],
gpgkeys: Vec<Url>,
include_etc_os_release: bool,
) -> Result<Self> {
let output = Python::with_gil(|py| {
// Resolve is a compiled in python module for resolving dependencies
let resolve =
PyModule::from_code(py, include_str!("resolve.py"), "resolve", "resolve")?;
let base = setup_base(py, repositories, &gpgkeys)?;

let etc_os_release = "/etc/os-release".to_string();
if include_etc_os_release && !pkg_specs.contains(&etc_os_release) {
pkg_specs.push(etc_os_release);
}

let args = PyTuple::new(py, &[base.to_object(py), pkg_specs.to_object(py)]);
// Run the resolve function, returning a json string, which we shall deserialize.
let val: String = resolve.getattr("resolve")?.call1(args)?.extract()?;
Expand All @@ -64,6 +70,7 @@ impl Lockfile {
cfg.contents.packages.clone(),
&cfg.contents.repositories,
cfg.contents.gpgkeys.clone(),
cfg.contents.os_release,
)
}

Expand Down Expand Up @@ -137,6 +144,7 @@ impl Lockfile {
requires,
&cfg.contents.repositories,
cfg.contents.gpgkeys.clone(),
cfg.contents.os_release,
)?;
lockfile.local_packages = self.local_packages.clone();
lockfile.pkg_specs = cfg.contents.packages.clone();
Expand Down
13 changes: 13 additions & 0 deletions tests/fixtures/no_auto_etc_os_release/rpmoci.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[contents]
gpgkeys = [
"https://raw.githubusercontent.com/microsoft/CBL-Mariner/2.0/SPECS/mariner-repos/MICROSOFT-RPM-GPG-KEY",
"https://packages.microsoft.com/keys/microsoft.asc",
]
packages = ["tini-static"]
os_release = false

[[contents.repositories]]
url = "https://packages.microsoft.com/cbl-mariner/2.0/prod/base/x86_64"

[image]
cmd = ["tini-static"]
31 changes: 31 additions & 0 deletions tests/it.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use std::{
process::Command,
};

use rpmoci::lockfile::Lockfile;

// Path to rpmoci binary under test
const EXE: &str = env!("CARGO_BIN_EXE_rpmoci");

Expand Down Expand Up @@ -158,6 +160,14 @@ fn test_simple_build() {
.status()
.unwrap();

// Open the lockfile and verify /etc/os-release was included as a dependency
let lockfile_path = root.join("rpmoci.lock");
eprintln!("lockfile_path: {}", lockfile_path.display());
let lockfile: Lockfile = toml::from_str(&fs::read_to_string(lockfile_path).unwrap()).unwrap();
assert!(lockfile
.iter_packages()
.any(|p| p.name == "mariner-release"));

// Cleanup using sudo
let _ = Command::new("sudo")
.arg("rm")
Expand Down Expand Up @@ -234,3 +244,24 @@ fn test_capabilities() {
.contains("cap_net_admin=ep"));
assert!(status.success());
}

#[test]
fn test_no_auto_etc_os_release() {
// Test that `contents.os_release = false` works
let root = setup_test("no_auto_etc_os_release");
let output = Command::new(EXE)
.arg("update")
.current_dir(&root)
.output()
.unwrap();
let stderr = std::str::from_utf8(&output.stderr).unwrap();
eprintln!("stderr: {}. {}. {}", stderr, root.display(), EXE);
assert!(output.status.success());
// Open the lockfile and verify /etc/os-release was not added as a dependency
let lockfile_path = root.join("rpmoci.lock");
eprintln!("lockfile_path: {}", lockfile_path.display());
let lockfile: Lockfile = toml::from_str(&fs::read_to_string(lockfile_path).unwrap()).unwrap();
assert!(!lockfile
.iter_packages()
.any(|p| p.name == "mariner-release"));
}

0 comments on commit a2e8f3d

Please sign in to comment.