diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml new file mode 100644 index 0000000..f9804be --- /dev/null +++ b/.github/actions/test/action.yml @@ -0,0 +1,16 @@ +name: "rpmoci tests" +runs: + using: "composite" + steps: + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: Setup rootless user + run: | + useradd -m -s /bin/bash rootless + - name: Run tests as non-root user + run: | + su - rootless + cargo test --features test-docker diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9685d1d..fee58de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ on: [push, pull_request] jobs: check: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 container: image: mcr.microsoft.com/cbl-mariner/base/core:2.0 steps: @@ -23,7 +23,7 @@ jobs: run: cargo clippy -- -D warnings test: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 container: image: mcr.microsoft.com/cbl-mariner/base/core:2.0 options: --privileged @@ -31,34 +31,16 @@ jobs: - name: Install dependencies run: unset HOME; tdnf install -y build-essential git openssl-devel python3-devel sudo ca-certificates dnf moby-cli skopeo shadow-utils sqlite-devel - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - uses: oras-project/setup-oras@v1 - with: - version: 1.1.0 - - name: Run cargo test - run: cargo test --features test-docker - - name: Setup rootless user - run: | - useradd -m -s /bin/bash rootless - echo "rootless:100000:65536" > /etc/subgid - echo "rootless:100000:65536" > /etc/subuid - - name: Build in rootless mode - run: | - su - rootless - cargo run -- build -f tests/fixtures/rootless/rpmoci.toml --image rootless --tag test + - uses: ./.github/actions/test.yml cargo-deny: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v3 - uses: EmbarkStudios/cargo-deny-action@v1 build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 container: image: mcr.microsoft.com/cbl-mariner/base/core:2.0 steps: diff --git a/.github/workflows/fedora.yml b/.github/workflows/fedora.yml new file mode 100644 index 0000000..97afea3 --- /dev/null +++ b/.github/workflows/fedora.yml @@ -0,0 +1,18 @@ +name: fedora + +on: [pull_request] + +jobs: + test: + runs-on: ubuntu-24.04 + container: + image: fedora:40 + options: --privileged + steps: + - name: Install dependencies + run: | + dnf install -y openssl-devel python3-devel sqlite-devel dnf-plugins-core + dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo + dnf install -y docker-ce-cli + - uses: actions/checkout@v2 + - uses: ./.github/actions/test.yml diff --git a/Cargo.lock b/Cargo.lock index 8607bd3..88303d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,9 +101,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "autocfg" @@ -219,9 +219,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.13" +version = "1.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +checksum = "2d74707dde2ba56f86ae90effb3b43ddd369504387e718014de010cec7959800" dependencies = [ "jobserver", "libc", @@ -234,12 +234,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "chrono" version = "0.4.38" @@ -256,9 +250,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.16" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" dependencies = [ "clap_builder", "clap_derive", @@ -276,9 +270,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" dependencies = [ "anstream", "anstyle", @@ -295,7 +289,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -330,9 +324,9 @@ checksum = "80e3adec7390c7643049466136117057188edf5f23efc5c8b4fc8079c8dc34a6" [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -377,7 +371,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -388,38 +382,38 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] name = "derive_builder" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" +checksum = "cd33f37ee6a119146a1781d3356a7c26028f83d779b2e04ecd45fdc75c76877b" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" +checksum = "7431fa049613920234f22c47fdc33e6cf3ee83067091ea4277a3f8c4587aae38" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] name = "derive_builder_macro" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" +checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc" dependencies = [ "derive_builder_core", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -432,7 +426,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -484,7 +478,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -495,7 +489,7 @@ checksum = "ba7795da175654fe16979af73f81f26a8ea27638d8d9823d317016888a63dc4c" dependencies = [ "num-traits", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -551,15 +545,15 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "filetime" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", @@ -569,9 +563,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c0596c1eac1f9e04ed902702e9878208b336edc9d6fddc8a48387349bab3666" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", "libz-sys", @@ -586,7 +580,7 @@ checksum = "2cd66269887534af4b0c3e3337404591daa8dc8b9b2b3db71f9523beb4bafb41" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -653,14 +647,14 @@ dependencies = [ [[package]] name = "getset" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" +checksum = "f636605b743120a8d32ed92fc27b6cde1a769f8f936c065151eb66f88ded513c" dependencies = [ - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.77", ] [[package]] @@ -746,9 +740,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", @@ -778,9 +772,9 @@ checksum = "5a611371471e98973dbcab4e0ec66c31a10bc356eeb4d54a0e05eac8158fe38c" [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" [[package]] name = "is_terminal_polyfill" @@ -850,9 +844,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.19" +version = "1.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc53a7799a7496ebc9fd29f31f7df80e83c9bda5299768af5f9e59eeea74647" +checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" dependencies = [ "cc", "pkg-config", @@ -928,18 +922,6 @@ dependencies = [ "adler2", ] -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "nom" version = "7.1.3" @@ -991,7 +973,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -1112,7 +1094,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -1161,27 +1143,25 @@ dependencies = [ ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "proc-macro-error-attr2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.109", - "version_check", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "proc-macro-error2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ + "proc-macro-error-attr2", "proc-macro2", "quote", - "version_check", + "syn 2.0.77", ] [[package]] @@ -1195,9 +1175,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831e8e819a138c36e212f3af3fd9eeffed6bf1510a805af35b0edee5ffa59433" +checksum = "15ee168e30649f7f234c3d49ef5a7a6cbf5134289bc46c29ff3155fa3221c225" dependencies = [ "cfg-if", "indoc", @@ -1213,9 +1193,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e8730e591b14492a8945cdff32f089250b05f5accecf74aeddf9e8272ce1fa8" +checksum = "e61cef80755fe9e46bb8a0b8f20752ca7676dcc07a5277d8b7768c6172e529b3" dependencies = [ "once_cell", "target-lexicon", @@ -1223,9 +1203,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e97e919d2df92eb88ca80a037969f44e5e70356559654962cbb3316d00300c6" +checksum = "67ce096073ec5405f5ee2b8b31f03a68e02aa10d5d4f565eca04acc41931fa1c" dependencies = [ "libc", "pyo3-build-config", @@ -1233,34 +1213,34 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb57983022ad41f9e683a599f2fd13c3664d7063a3ac5714cae4b7bee7d3f206" +checksum = "2440c6d12bc8f3ae39f1e775266fa5122fd0c8891ce7520fa6048e683ad3de28" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] name = "pyo3-macros-backend" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec480c0c51ddec81019531705acac51bcdbeae563557c982aa8263bb96880372" +checksum = "1be962f0e06da8f8465729ea2cb71a416d2257dff56cbe40a70d3e62a93ae5d1" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1297,9 +1277,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ "bitflags", ] @@ -1374,7 +1354,6 @@ dependencies = [ "flate2", "glob", "log", - "nix", "ocidir", "pathdiff", "pyo3", @@ -1408,18 +1387,18 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags", "errno", @@ -1459,29 +1438,29 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -1554,7 +1533,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -1570,9 +1549,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.75" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -1647,7 +1626,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -1713,9 +1692,9 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" @@ -1809,7 +1788,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", "wasm-bindgen-shared", ] @@ -1831,7 +1810,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1999,7 +1978,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 26218a4..eff60d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,11 +20,6 @@ filetime = "0.2.22" flate2 = { version = "1.0.24", features = ["zlib"], default-features = false } glob = "0.3.0" log = "0.4.19" -nix = { version = "0.29.0", features = [ - "sched", - "signal", - "user", -], default-features = false } pathdiff = "0.2.1" pyo3 = { version = "0.22.1", features = ["auto-initialize"] } rpm = { version = "0.15.0", default-features = false } diff --git a/README.md b/README.md index 34665c3..e1e21ad 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ rpmoci builds OCI container images from RPM packages, using [DNF](https://github rpmoci features: - **deterministic** rpmoci locks RPM dependencies using the package file/lockfile paradigm of bundler/cargo etc and can produce reproducible images with identical digests. - - **unprivileged** rpmoci can build images in environments without access to a container runtime, and without root access (this relies on the user being able to create [user namespaces](https://www.man7.org/linux/man-pages/man7/user_namespaces.7.html)) + - **unprivileged** rpmoci can build images in environments without access to a container runtime, and can also run in a user namespace. - **small** rpmoci images are built solely from the RPMs you request and their dependencies, so don't contain unnecessary dependencies. rpmoci is a good fit for containerizing applications - you package your application as an RPM, and then use rpmoci to build a minimal container image from that RPM. @@ -32,13 +32,12 @@ Per the above, you'll need dnf, Rust, python3-devel and openssl-devel installed. cargo build ``` -### Rootless setup -When rpmoci runs as a non-root user it will automatically attempt to setup a user namespace in which to run. -rpmoci maps the user's uid/gid to root in the user namespace. +### Rootless +rpmoci can create images as a non-root user using [user namespaces](https://man7.org/linux/man-pages/man7/user_namespaces.7.html). -It also attempts to map the current user's subuid/subgid range into the user namespace, which is required for rpmoci to be able to create containers from RPMs that contain files owned by a non-root user. - -rpmoci requires that at least 999 subuids/subgids are allocated to your user. You can create them per [https://rootlesscontaine.rs/getting-started/common/subuid/](https://rootlesscontaine.rs/getting-started/common/subuid/). +```bash +$ unshare --map-auto --map-root-user --user rpmoci build --image foo --tag bar +``` ## Getting started You need to create an rpmoci.toml file. An example is: diff --git a/deny.toml b/deny.toml index 26453fa..00a8701 100644 --- a/deny.toml +++ b/deny.toml @@ -1,7 +1,7 @@ +[graph] targets = [{ triple = "x86_64-unknown-linux-gnu" }] [licenses] -unlicensed = "deny" # only allow GPL-3.0 compatible licenses allow = [ "MIT", @@ -11,6 +11,4 @@ allow = [ "GPL-3.0", "BSD-3-Clause", ] -deny = ["GPL-2.0"] -default = "deny" confidence-threshold = 1.0 diff --git a/src/lib.rs b/src/lib.rs index 4480f49..e593793 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,6 @@ mod archive; pub mod cli; pub mod config; pub mod lockfile; -pub mod subid; pub mod write; use anyhow::Result; use cli::Command; diff --git a/src/lockfile/resolve.rs b/src/lockfile/resolve.rs index a5fd8fe..d3cbc27 100644 --- a/src/lockfile/resolve.rs +++ b/src/lockfile/resolve.rs @@ -185,6 +185,26 @@ impl<'a> Drop for Base<'a> { } } +fn home_dir() -> Option { + // The home_dir bugs on windows are irrelevant as rpmoci is linux only + #![allow(deprecated)] + std::env::home_dir() +} + +/// Return a directory to use for caching dnf data +fn cache_dir() -> Option { + std::env::var_os("XDG_CACHE_HOME") + .and_then(|s| { + if s.is_empty() { + None + } else { + Some(PathBuf::from(s)) + } + }) + .or_else(|| home_dir().map(|p| p.join(".cache"))) + .map(|p| p.join("rpmoci")) +} + /// Initialize the dnf.Base object with the repositories configured in the rpmoci.toml /// The Base object also initializes and configures any system defined plugins pub(crate) fn setup_base<'a>( @@ -196,10 +216,9 @@ pub(crate) fn setup_base<'a>( let base = dnf.getattr("Base")?.call0()?; let conf = base.getattr("conf")?; - // Set up caching and log dir to the value of RPMOCI_CACHE_DIR if it's set. - // When running in rootless mode rpmoci will set that, otherwise dnf will select - // diretory the user can't write to. - if let Ok(cache_dir) = env::var("RPMOCI_CACHE_DIR") { + // To support running in a user namespace override the cache and log directories + // as dnf will choose a dnf only root can write to. + if let Some(cache_dir) = cache_dir() { conf.setattr("cachedir", &cache_dir)?; conf.setattr("logdir", &cache_dir)?; } diff --git a/src/main.rs b/src/main.rs index 2003ad6..de1a68e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,30 +12,10 @@ //! //! You should have received a copy of the GNU General Public License //! along with this program. If not, see . -use std::{ - os::{fd::AsRawFd, unix::process::CommandExt}, - process::Command, -}; -use anyhow::{bail, Context, Result}; +use anyhow::Result; use clap::Parser; -use nix::{ - libc::c_int, - sched::CloneFlags, - sys::{ - signal::{ - self, - Signal::{self, SIGCHLD}, - }, - wait::waitpid, - }, - unistd::{close, getgid, getuid, pipe, read}, -}; -use pyo3::{ - types::{PyAnyMethods, PyModule}, - Python, -}; -use rpmoci::{subid::setup_id_maps, write}; +use rpmoci::write; fn main() { if let Err(err) = try_main() { @@ -47,90 +27,10 @@ fn main() { } } -unsafe fn run_in_userns() -> anyhow::Result<()> { - // dnf needs to be run as root, but given that rpmoci only needs to query package repos - // and/or install packages into an install root, we can run in a user namespace, mapping - // the current uid/gid to root - // this function spawns a child process in a new user namespace, the parent configures - // the uid/gid mappings, then signals the child to re-exec rpmoci - - let user_id = getuid(); - let group_id = getgid(); - let cache_dir = - get_cache_dir().context("Failed to determine a user-writable cache dir for dnf")?; - - const STACK_SIZE: usize = 1024 * 1024; - let stack: &mut [u8; STACK_SIZE] = &mut [0; STACK_SIZE]; - // this pipe is for the parent to notify the child when user namespaces mappings - // have been configured - let (reader, writer) = pipe()?; - // create child process with a new user namespace - let child = nix::sched::clone( - Box::new(|| { - // this child process just waits for the parent to notify it before re-execing - close(writer.as_raw_fd()).unwrap(); - - read( - reader.as_raw_fd(), - // Pass a non-zero length buffer to read() to ensure the child blocks - &mut [0u8; 1], - ) - .unwrap(); - Command::new(std::env::current_exe().unwrap()) - .args(std::env::args().skip(1)) - .env("RPMOCI_CACHE_DIR", cache_dir.clone()) - .exec(); - 255 - }), - stack, - CloneFlags::CLONE_NEWUSER | CloneFlags::CLONE_NEWNS, - Some(SIGCHLD as c_int), - ) - .context("Clone failed")?; - - // this parent process sets up user namespace mappings, notifies the child to continue, - // then waits for the child to exit - close(reader.as_raw_fd())?; - // Kill the child process if we fail to setup the uid/gid mappings - if let Err(e) = - setup_id_maps(child, user_id, group_id).context("Failed to setup uid/gid mappings") - { - signal::kill(child, Signal::SIGTERM)?; - waitpid(child, None)?; - return Err(e); - } - close(writer.as_raw_fd())?; - let status = waitpid(child, None)?; - if let nix::sys::wait::WaitStatus::Exited(_, code) = status { - // Exit immediately with the child's exit code, as the child should have - // have already printed any error messages on completion - std::process::exit(code); - } else { - bail!("Child process failed"); - } -} - -fn get_cache_dir() -> Result { - Python::with_gil(|py| { - let misc = PyModule::import_bound(py, "dnf.yum.misc")?; - misc.call_method0("getCacheDir")?; - Ok(misc.getattr("getCacheDir")?.call0()?.extract()?) - }) -} - fn try_main() -> Result<()> { let args = rpmoci::cli::Cli::parse(); env_logger::Builder::new() .filter_level(args.verbose.log_level_filter()) .init(); - - // `rpmoci build` is the only command that needs to run as root. - // If a user specifies this command when running as a non-root user, then try and run - // in rootless mode using a user namespace - if matches!(args.command, rpmoci::cli::Command::Build { .. }) && !getuid().is_root() { - unsafe { - run_in_userns().context("Failed to run rpmoci in rootless mode. See https://github.com/microsoft/rpmoci#rootless-setup, or re-run as root")?; - } - } rpmoci::main(args.command) } diff --git a/src/subid.rs b/src/subid.rs deleted file mode 100644 index 87311df..0000000 --- a/src/subid.rs +++ /dev/null @@ -1,241 +0,0 @@ -//! Function related to user namespace setup -//! -//! Copyright (C) Microsoft Corporation. -//! -//! This program is free software: you can redistribute it and/or modify -//! it under the terms of the GNU General Public License as published by -//! the Free Software Foundation, either version 3 of the License, or -//! (at your option) any later version. -//! -//! This program is distributed in the hope that it will be useful, -//! but WITHOUT ANY WARRANTY; without even the implied warranty of -//! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -//! GNU General Public License for more details. -//! -//! You should have received a copy of the GNU General Public License -//! along with this program. If not, see . -use anyhow::{bail, Context, Result}; -use nix::unistd::{Gid, Group, Pid, Uid, User}; -use std::{ - fs::File, - io::{self, read_to_string, Read}, - process::Command, -}; - -use crate::write; - -/// Represents a range of sub uid/gids -#[derive(Debug, PartialEq)] -pub struct SubIdRange { - /// The first id in the range - pub start: usize, - /// The number of ids in the range - pub count: usize, -} - -const ETC_SUBUID: &str = "/etc/subuid"; -const ETC_SUBGID: &str = "/etc/subgid"; - -/// Create new uid/gid mappings for the current user/group, -/// using the values in /etc/subuid and /etc/subgid -pub fn setup_id_maps(child: Pid, uid: Uid, gid: Gid) -> anyhow::Result<()> { - let username = User::from_uid(uid).ok().flatten().map(|user| user.name); - let uid_string = uid.to_string(); - let (newuidmap_args, subuid_count) = newidmap_args( - File::open(ETC_SUBUID).context("Failed to open /etc/subuid")?, - &uid_string, - username.as_deref(), - child, - ) - .context("Failed to read subuids from /etc/subuid")?; - - let groupname = Group::from_gid(gid) - .ok() - .flatten() - .map(|group: Group| group.name); - let gid_string = gid.to_string(); - let (newgidmap_args, subgid_count) = newidmap_args( - File::open(ETC_SUBGID).context("Failed to open /etc/subgid")?, - &gid_string, - groupname.as_deref(), - child, - ) - .context("Failed to read subgids from /etc/subgid")?; - - if subuid_count < 1000 { - write::error( - "Error", - "At least 1000 subuids must be configured for the current user in /etc/subuid", - )?; - } - if subgid_count < 1000 { - write::error( - "Error", - "At least 1000 subgids must be configured for the current group in /etc/subgid", - )?; - } - if subuid_count < 1000 || subgid_count < 1000 { - bail!("Not enough subids available"); - } - - let status = Command::new("newuidmap").args(newuidmap_args).status()?; - if !status.success() { - bail!("Failed to create uid mappings"); - } - - let status = Command::new("newgidmap").args(newgidmap_args).status()?; - if !status.success() { - bail!("Failed to create gid mappings"); - } - - Ok(()) -} - -// Determine the newuidmap/newgidmap arguments to configure sub ids, -// and the number of ids that will be mapped -fn newidmap_args( - etc_subid: impl Read, - id: &str, - name: Option<&str>, - child: Pid, -) -> Result<(Vec, usize)> { - let mut args = vec![ - child.to_string(), - "0".to_string(), - id.to_string(), - "1".to_string(), - ]; - - let mut next_id = 1; - for range in get_sub_id_ranges(etc_subid, id, name)? { - args.push(next_id.to_string()); - args.push(range.start.to_string()); - args.push(range.count.to_string()); - next_id += range.count; - } - Ok((args, next_id)) -} - -/// Get the subid ranges for a user or group -fn get_sub_id_ranges( - subid_file: impl Read, - id: &str, - name: Option<&str>, -) -> io::Result> { - Ok(read_to_string(subid_file)? - .lines() // split the string into an iterator of string slices - .filter_map(|line| { - let parts = line.splitn(3, ':').collect::>(); - if parts.len() != 3 { - // Not a valid line - return None; - } - if Some(parts[0]) != name && parts[0] != id { - // Not a line for the desired user/group - return None; - } - if let (Ok(start), Ok(count)) = (parts[1].parse::(), parts[2].parse::()) { - Some(SubIdRange { start, count }) - } else { - None - } - }) - .collect()) -} - -#[cfg(test)] -mod tests { - - use nix::unistd::Pid; - - use super::{get_sub_id_ranges, SubIdRange}; - - #[test] - fn test_get_sub_id_ranges() { - let subid_contents = r#" -# this is a comment -user1:100:500 -user2:10:10 -user3:1000:65536 -user1:1:8 -1000:100000:5 - "#; - assert_eq!( - get_sub_id_ranges(subid_contents.as_bytes(), "1000", None).unwrap(), - vec![SubIdRange { - start: 100000, - count: 5 - }] - ); - assert_eq!( - get_sub_id_ranges(subid_contents.as_bytes(), "1000", Some("user1")).unwrap(), - vec![ - SubIdRange { - start: 100, - count: 500 - }, - SubIdRange { start: 1, count: 8 }, - SubIdRange { - start: 100000, - count: 5 - } - ] - ); - assert_eq!( - get_sub_id_ranges(subid_contents.as_bytes(), "1001", Some("user1")).unwrap(), - vec![ - SubIdRange { - start: 100, - count: 500 - }, - SubIdRange { start: 1, count: 8 } - ] - ); - assert_eq!( - get_sub_id_ranges(subid_contents.as_bytes(), "1001", Some("user2")).unwrap(), - vec![SubIdRange { - start: 10, - count: 10 - }] - ); - } - - #[test] - fn test_newidmap_args() { - let subid_contents = r#" -# this is a comment -user1:100:500 -user2:10:10 -user3:1000:65536 -user1:1:8 -1000:100000:5 - "#; - assert_eq!( - super::newidmap_args( - subid_contents.as_bytes(), - "1000", - Some("user1"), - Pid::from_raw(1234) - ) - .unwrap(), - ( - vec![ - "1234".to_string(), - "0".to_string(), - "1000".to_string(), - "1".to_string(), - "1".to_string(), - "100".to_string(), - "500".to_string(), - "501".to_string(), - "1".to_string(), - "8".to_string(), - "509".to_string(), - "100000".to_string(), - "5".to_string() - ], - 514 - ) - ); - } -} diff --git a/tests/it.rs b/tests/it.rs index 09f14b0..551fbd9 100644 --- a/tests/it.rs +++ b/tests/it.rs @@ -1,5 +1,4 @@ //! Integration Tests for rpmoci - use std::{ fs::{self}, path::{Path, PathBuf}, @@ -14,6 +13,15 @@ use test_temp_dir::TestTempDir; // Path to rpmoci binary under test const EXE: &str = env!("CARGO_BIN_EXE_rpmoci"); +fn rpmoci() -> Command { + // if running as root + let mut cmd = Command::new("unshare"); + // Don't use --map-auto here as that doesn't work on Azure Linux 2.0's unshare + // This will cause failures if tests install RPMs which create users + cmd.arg("--map-root-user").arg("--user").arg(EXE); + cmd +} + fn setup_test(fixture: &str) -> (TestTempDir, PathBuf) { // the test_temp_dir macro can't handle the integration test module path not containing ::, // so construct our own item path @@ -42,7 +50,7 @@ fn setup_test(fixture: &str) -> (TestTempDir, PathBuf) { fn test_incompatible_lockfile() { // Building with locked should fail let (_tmp_dir, root) = setup_test("incompatible_lockfile"); - let output = Command::new(EXE) + let output = rpmoci() .arg("build") .arg("--locked") .args(["--image=foo", "--tag=bar"]) @@ -55,18 +63,14 @@ fn test_incompatible_lockfile() { assert!(stderr.contains("needs to be updated but --locked was passed to prevent this")); // Updating should succeed - let output = Command::new(EXE) - .arg("update") - .current_dir(&root) - .output() - .unwrap(); + let output = rpmoci().arg("update").current_dir(&root).output().unwrap(); assert!(output.status.success()); } #[test] fn test_updatable_lockfile() { let (_tmp_dir, root) = setup_test("updatable_lockfile"); - let output = Command::new(EXE) + let output = rpmoci() .arg("update") .current_dir(root) .env("NO_COLOR", "YES") // So the stderr checks below work @@ -85,7 +89,7 @@ fn test_updatable_lockfile() { fn test_unparseable_lockfile() { let (_tmp_dir, root) = setup_test("unparseable_lockfile"); // building with --locked should fail - let output = Command::new(EXE) + let output = rpmoci() .arg("build") .arg("--locked") .args(["--image=foo", "--tag=bar"]) @@ -98,7 +102,7 @@ fn test_unparseable_lockfile() { assert!(stderr.contains("failed to parse existing lock file")); // but we should be able to update it - let output = Command::new(EXE) + let output = rpmoci() .arg("update") .current_dir(root) .env("NO_COLOR", "YES") // So the stderr checks below work @@ -114,7 +118,7 @@ fn test_unparseable_lockfile() { fn test_no_lockfile() { let (_tmp_dir, root) = setup_test("no_lockfile"); // building with --locked should fail - let output = Command::new(EXE) + let output = rpmoci() .arg("build") .arg("--locked") .args(["--image=foo", "--tag=bar"]) @@ -132,7 +136,7 @@ fn test_no_lockfile() { #[test] fn test_update_from_lockfile() { let (_tmp_dir, root) = setup_test("update_from_lockfile"); - let output = Command::new(EXE) + let output = rpmoci() .arg("update") .arg("--from-lockfile") .current_dir(root) @@ -146,14 +150,12 @@ fn test_update_from_lockfile() { } // Do a simple container image build, verifying the reproducibility and /etc/os-release dependency. -// This test requires oras be installed, to check that the produced images are -// compatible with another OCI tool. #[test] fn test_simple_build() { // Repeat the same build twice using same SOURCE_DATE_EPOCH and ensure the resulting images are identical let (_tmp_dir, root) = setup_test("simple_build"); let source_date_epoch = "1701168547"; - let output1 = Command::new(EXE) + let output1 = rpmoci() .arg("build") .arg("--image=foo") .arg("--tag=bar") @@ -166,16 +168,6 @@ fn test_simple_build() { eprintln!("stderr: {}", stderr); assert!(output1.status.success()); - let oras_status = Command::new("oras") - .arg("copy") - .arg("--from-oci-layout") - .arg("--to-oci-layout") - .arg("foo:bar") - .arg("baz:bar") - .current_dir(&root) - .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()); @@ -187,11 +179,10 @@ fn test_simple_build() { let stderr = std::str::from_utf8(&output1.stderr).unwrap(); eprintln!("stderr: {}", stderr); assert!(output1.status.success()); - assert!(oras_status.success()); // Repeat the build, to ensure reproducing the same image works std::thread::sleep(std::time::Duration::from_secs(1)); - let output2 = Command::new(EXE) + let output2 = rpmoci() .arg("build") .arg("--image=foo") .arg("--tag=bar2") @@ -211,7 +202,7 @@ fn test_simple_build() { #[test] fn test_simple_vendor() { let (_tmp_dir, root) = setup_test("simple_vendor"); - let output = Command::new(EXE) + let output = rpmoci() .arg("update") .current_dir(&root) .env("NO_COLOR", "YES") // So the stderr checks below work @@ -221,7 +212,7 @@ fn test_simple_vendor() { eprintln!("stderr: {}. {}. {}", stderr, root.display(), EXE); assert!(output.status.success()); - let output = Command::new(EXE) + let output = rpmoci() .arg("vendor") .arg("--out-dir=.") .current_dir(&root) @@ -237,11 +228,7 @@ fn test_simple_vendor() { fn test_no_auto_etc_os_release() { // Test that `contents.os_release = false` works let (_tmp_dir, root) = setup_test("no_auto_etc_os_release"); - let output = Command::new(EXE) - .arg("update") - .current_dir(&root) - .output() - .unwrap(); + let output = rpmoci().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()); @@ -258,11 +245,7 @@ fn test_no_auto_etc_os_release() { fn test_explicit_etc_os_release() { // Test that resolution works when /etc/os-release explicitly added let (_tmp_dir, root) = setup_test("etc_os_release_explicit"); - let output = Command::new(EXE) - .arg("update") - .current_dir(&root) - .output() - .unwrap(); + let output = rpmoci().arg("update").current_dir(&root).output().unwrap(); let stderr = std::str::from_utf8(&output.stderr).unwrap(); eprintln!("stderr: {}. {}. {}", stderr, root.display(), EXE); } @@ -271,7 +254,7 @@ fn test_explicit_etc_os_release() { fn test_weak_deps() { // Verify a build without weak dependencies succeeds let (_tmp_dir, root) = setup_test("weakdeps"); - let output = Command::new(EXE) + let output = rpmoci() .arg("build") .arg("--image=weak") .arg("--tag=deps") @@ -305,21 +288,21 @@ fn test_hardlinks() { fn build_and_run(image: &str) -> std::process::Output { let (_tmp_dir, root) = setup_test(image); - let status = Command::new(EXE) + let status = rpmoci() .arg("build") .arg("--image") .arg(image) .arg("--tag=test") .current_dir(&root) .status() - .unwrap(); + .expect("failed to run rpmoci"); assert!(status.success()); copy_to_docker(image, &root); let output = Command::new("docker") .arg("run") .arg(format!("{}:test", image)) .output() - .unwrap(); + .expect("failed to run container"); assert!(output.status.success()); output } @@ -331,6 +314,6 @@ fn copy_to_docker(image: &str, root: impl AsRef) { .arg(format!("docker-daemon:{}:test", image)) .current_dir(root.as_ref()) .status() - .unwrap(); + .expect("failed to run skopeo"); assert!(status.success()); }