ostree_ext/container/
skopeo.rs1use super::ImageReference;
4use anyhow::{Context, Result};
5use cap_std_ext::cmdext::{CapStdExtCommandExt, CmdFds};
6use containers_image_proxy::oci_spec::image as oci_image;
7use fn_error_context::context;
8use io_lifetimes::OwnedFd;
9use serde::Deserialize;
10use std::io::Read;
11use std::path::Path;
12use std::process::Stdio;
13use std::str::FromStr;
14use tokio::process::Command;
15
16const POLICY_PATH: &str = "/etc/containers/policy.json";
21const INSECURE_ACCEPT_ANYTHING: &str = "insecureAcceptAnything";
22
23#[derive(Deserialize)]
24struct PolicyEntry {
25 #[serde(rename = "type")]
26 ty: String,
27}
28#[derive(Deserialize)]
29struct ContainerPolicy {
30 default: Option<Vec<PolicyEntry>>,
31}
32
33impl ContainerPolicy {
34 fn is_default_insecure(&self) -> bool {
35 if let Some(default) = self.default.as_deref() {
36 match default.split_first() {
37 Some((v, &[])) => v.ty == INSECURE_ACCEPT_ANYTHING,
38 _ => false,
39 }
40 } else {
41 false
42 }
43 }
44}
45
46pub(crate) fn container_policy_is_default_insecure() -> Result<bool> {
47 let r = std::io::BufReader::new(std::fs::File::open(POLICY_PATH)?);
48 let policy: ContainerPolicy = serde_json::from_reader(r)?;
49 Ok(policy.is_default_insecure())
50}
51
52pub(crate) fn new_cmd() -> std::process::Command {
54 let mut cmd = std::process::Command::new(bootc_utils::skopeo_bin());
55 cmd.stdin(Stdio::null());
56 cmd
57}
58
59pub(crate) fn spawn(mut cmd: Command) -> Result<tokio::process::Child> {
61 let cmd = cmd.stdin(Stdio::null()).stderr(Stdio::piped());
62 cmd.spawn().context("Failed to exec skopeo")
63}
64
65#[context("Skopeo copy")]
67pub async fn copy(
68 src: &ImageReference,
69 dest: &ImageReference,
70 authfile: Option<&Path>,
71 add_fd: Option<(std::sync::Arc<OwnedFd>, i32)>,
72 progress: bool,
73) -> Result<oci_image::Digest> {
74 let digestfile = tempfile::NamedTempFile::new()?;
75 let mut cmd = new_cmd();
76 cmd.arg("copy");
77 if !progress {
78 cmd.stdout(std::process::Stdio::null());
79 }
80 cmd.arg("--digestfile");
81 cmd.arg(digestfile.path());
82 if let Some((add_fd, n)) = add_fd {
83 let mut fds = CmdFds::new();
84 fds.take_fd_n(add_fd, n);
85 cmd.take_fds(fds);
86 }
87 if let Some(authfile) = authfile {
88 cmd.arg("--authfile");
89 cmd.arg(authfile);
90 }
91 cmd.args(&[src.to_string(), dest.to_string()]);
92 let mut cmd = tokio::process::Command::from(cmd);
93 cmd.kill_on_drop(true);
94 let proc = super::skopeo::spawn(cmd)?;
95 let output = proc.wait_with_output().await?;
96 if !output.status.success() {
97 let stderr = String::from_utf8_lossy(&output.stderr);
98 return Err(anyhow::anyhow!("skopeo failed: {}\n", stderr));
99 }
100 let mut digestfile = digestfile.into_file();
101 let mut r = String::new();
102 digestfile.read_to_string(&mut r)?;
103 Ok(oci_image::Digest::from_str(r.trim())?)
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 const DEFAULT_POLICY: &str = indoc::indoc! {r#"
112 {
113 "default": [
114 {
115 "type": "insecureAcceptAnything"
116 }
117 ],
118 "transports":
119 {
120 "docker-daemon":
121 {
122 "": [{"type":"insecureAcceptAnything"}]
123 }
124 }
125 }
126 "#};
127
128 const REASONABLY_LOCKED_DOWN: &str = indoc::indoc! { r#"
130 {
131 "default": [{"type": "reject"}],
132 "transports": {
133 "dir": {
134 "": [{"type": "insecureAcceptAnything"}]
135 },
136 "atomic": {
137 "hostname:5000/myns/official": [
138 {
139 "type": "signedBy",
140 "keyType": "GPGKeys",
141 "keyPath": "/path/to/official-pubkey.gpg"
142 }
143 ]
144 }
145 }
146 }
147 "#};
148
149 #[test]
150 fn policy_is_insecure() {
151 let p: ContainerPolicy = serde_json::from_str(DEFAULT_POLICY).unwrap();
152 assert!(p.is_default_insecure());
153 for &v in &["{}", REASONABLY_LOCKED_DOWN] {
154 let p: ContainerPolicy = serde_json::from_str(v).unwrap();
155 assert!(!p.is_default_insecure());
156 }
157 }
158}