Skip to content

Commit 645c6c3

Browse files
authored
Merge pull request #39 from cgwalters/auth-fd
2 parents 356fb92 + 9717452 commit 645c6c3

File tree

2 files changed

+63
-4
lines changed

2 files changed

+63
-4
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ serde_json = "1.0.64"
2121
semver = "1.0.4"
2222
tokio = { features = ["fs", "io-util", "macros", "process", "rt", "sync"], version = "1" }
2323
tracing = "0.1"
24+
cap-tempfile = "1.0.1"
25+
cap-std-ext = "1.0"
2426

2527
[lib]
2628
path = "src/imageproxy.rs"

src/imageproxy.rs

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
//! More information: <https://github.com/containers/skopeo/pull/1476>
66
77
use anyhow::{anyhow, Context, Result};
8+
use cap_std_ext::cap_std;
9+
use cap_std_ext::prelude::CapStdExtCommandExt;
810
use futures_util::Future;
911
use nix::sys::socket::{self as nixsocket, ControlMessageOwned};
1012
use nix::sys::uio::IoVec;
1113
use once_cell::sync::Lazy;
1214
use serde::{Deserialize, Serialize};
1315
use std::fs::File;
16+
use std::ops::Range;
1417
use std::os::unix::io::AsRawFd;
1518
use std::os::unix::prelude::{CommandExt, FromRawFd, RawFd};
1619
use std::path::PathBuf;
@@ -25,6 +28,11 @@ use tracing::instrument;
2528
pub const OCI_TYPE_LAYER_GZIP: &str = "application/vnd.oci.image.layer.v1.tar+gzip";
2629
pub const OCI_TYPE_LAYER_TAR: &str = "application/vnd.oci.image.layer.v1.tar";
2730

31+
/// File descriptor range which is reserved for passing data down into the proxy;
32+
/// avoid configuring the command to use files in this range. (Also, stdin is
33+
/// reserved)
34+
pub const RESERVED_FD_RANGE: Range<i32> = 100..200;
35+
2836
// This is defined in skopeo; maximum size of JSON we will read/write.
2937
// Note that payload data (non-metadata) should go over a pipe file descriptor.
3038
const MAX_MSG_SIZE: usize = 32 * 1024;
@@ -119,8 +127,12 @@ fn file_from_scm_rights(cmsg: ControlMessageOwned) -> Option<File> {
119127
#[derive(Debug, Default)]
120128
pub struct ImageProxyConfig {
121129
/// Path to container auth file; equivalent to `skopeo --authfile`.
130+
/// This conflicts with [`auth_data`].
122131
pub authfile: Option<PathBuf>,
123132

133+
/// Data stream for container auth. This conflicts with [`authfile`].
134+
pub auth_data: Option<File>,
135+
124136
/// Do not use default container authentication paths; equivalent to `skopeo --no-creds`.
125137
///
126138
/// Defaults to `false`; in other words, use the default file paths from `man containers-auth.json`.
@@ -160,6 +172,13 @@ impl TryFrom<ImageProxyConfig> for Command {
160172
type Error = anyhow::Error;
161173

162174
fn try_from(config: ImageProxyConfig) -> Result<Self, Self::Error> {
175+
let mut allocated_fds = RESERVED_FD_RANGE.clone();
176+
let mut alloc_fd = || {
177+
allocated_fds
178+
.next()
179+
.ok_or_else(|| anyhow::anyhow!("Ran out of reserved file descriptors for child"))
180+
};
181+
163182
// By default, we set up pdeathsig to "lifecycle bind" the child process to us.
164183
let mut c = config.skopeo_cmd.unwrap_or_else(|| {
165184
let mut c = std::process::Command::new("skopeo");
@@ -172,16 +191,40 @@ impl TryFrom<ImageProxyConfig> for Command {
172191
c
173192
});
174193
c.arg("experimental-image-proxy");
194+
let auth_option_count = [
195+
config.authfile.is_some(),
196+
config.auth_data.is_some(),
197+
config.auth_anonymous,
198+
]
199+
.into_iter()
200+
.filter(|&x| x)
201+
.count();
202+
if auth_option_count > 1 {
203+
// This is a programmer error really
204+
anyhow::bail!("Conflicting authentication options");
205+
}
175206
if let Some(authfile) = config.authfile {
176-
if config.auth_anonymous {
177-
// This is a programmer error really
178-
anyhow::bail!("Cannot use anonymous auth and an authfile");
179-
}
180207
c.arg("--authfile");
181208
c.arg(authfile);
209+
} else if let Some(mut auth_data) = config.auth_data.map(std::io::BufReader::new) {
210+
// If we get the authentication data as a file, we always copy it to a new temporary file under
211+
// the assumption that the caller provided it this way to aid in privilege separation where
212+
// the file is only readable to privileged code.
213+
let target_fd = alloc_fd()?;
214+
let tmpd = &cap_std::fs::Dir::open_ambient_dir("/tmp", cap_std::ambient_authority())?;
215+
let mut tempfile = cap_tempfile::TempFile::new_anonymous(tmpd)
216+
.context("Creating temporary file for auth data")
217+
.map(std::io::BufWriter::new)?;
218+
std::io::copy(&mut auth_data, &mut tempfile)?;
219+
let tempfile = tempfile.into_inner()?.into_std();
220+
let fd = std::sync::Arc::new(tempfile.into());
221+
c.take_fd_n(fd, target_fd);
222+
c.arg("--authfile");
223+
c.arg(format!("/proc/self/fd/{target_fd}"));
182224
} else if config.auth_anonymous {
183225
c.arg("--no-creds");
184226
}
227+
185228
if let Some(certificate_directory) = config.certificate_directory {
186229
c.arg("--cert-dir");
187230
c.arg(certificate_directory);
@@ -482,6 +525,8 @@ impl ImageProxy {
482525

483526
#[cfg(test)]
484527
mod tests {
528+
use std::io::{Seek, Write};
529+
485530
use super::*;
486531

487532
fn validate(c: Command, contains: &[&str], not_contains: &[&str]) {
@@ -499,6 +544,8 @@ mod tests {
499544

500545
#[test]
501546
fn proxy_configs() {
547+
let tmpd = &cap_tempfile::tempdir(cap_std::ambient_authority()).unwrap();
548+
502549
let c = Command::try_from(ImageProxyConfig::default()).unwrap();
503550
validate(
504551
c,
@@ -526,5 +573,15 @@ mod tests {
526573
})
527574
.unwrap();
528575
validate(c, &[r"--tls-verify=false"], &[]);
576+
577+
let mut tmpf = cap_tempfile::TempFile::new_anonymous(tmpd).unwrap();
578+
tmpf.write_all(r#"{ "auths": {} "#.as_bytes()).unwrap();
579+
tmpf.seek(std::io::SeekFrom::Start(0)).unwrap();
580+
let c = Command::try_from(ImageProxyConfig {
581+
auth_data: Some(tmpf.into_std()),
582+
..Default::default()
583+
})
584+
.unwrap();
585+
validate(c, &["--authfile", "/proc/self/fd/100"], &[]);
529586
}
530587
}

0 commit comments

Comments
 (0)