ostree_ext/container/
skopeo.rs1use super::ImageReference;
4use anyhow::{Context, Result};
5use cap_std_ext::cmdext::CapStdExtCommandExt;
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("skopeo");
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 cmd.take_fd_n(add_fd, n);
84 }
85 if let Some(authfile) = authfile {
86 cmd.arg("--authfile");
87 cmd.arg(authfile);
88 }
89 cmd.args(&[src.to_string(), dest.to_string()]);
90 let mut cmd = tokio::process::Command::from(cmd);
91 cmd.kill_on_drop(true);
92 let proc = super::skopeo::spawn(cmd)?;
93 let output = proc.wait_with_output().await?;
94 if !output.status.success() {
95 let stderr = String::from_utf8_lossy(&output.stderr);
96 return Err(anyhow::anyhow!("skopeo failed: {}\n", stderr));
97 }
98 let mut digestfile = digestfile.into_file();
99 let mut r = String::new();
100 digestfile.read_to_string(&mut r)?;
101 Ok(oci_image::Digest::from_str(r.trim())?)
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 const DEFAULT_POLICY: &str = indoc::indoc! {r#"
110 {
111 "default": [
112 {
113 "type": "insecureAcceptAnything"
114 }
115 ],
116 "transports":
117 {
118 "docker-daemon":
119 {
120 "": [{"type":"insecureAcceptAnything"}]
121 }
122 }
123 }
124 "#};
125
126 const REASONABLY_LOCKED_DOWN: &str = indoc::indoc! { r#"
128 {
129 "default": [{"type": "reject"}],
130 "transports": {
131 "dir": {
132 "": [{"type": "insecureAcceptAnything"}]
133 },
134 "atomic": {
135 "hostname:5000/myns/official": [
136 {
137 "type": "signedBy",
138 "keyType": "GPGKeys",
139 "keyPath": "/path/to/official-pubkey.gpg"
140 }
141 ]
142 }
143 }
144 }
145 "#};
146
147 #[test]
148 fn policy_is_insecure() {
149 let p: ContainerPolicy = serde_json::from_str(DEFAULT_POLICY).unwrap();
150 assert!(p.is_default_insecure());
151 for &v in &["{}", REASONABLY_LOCKED_DOWN] {
152 let p: ContainerPolicy = serde_json::from_str(v).unwrap();
153 assert!(!p.is_default_insecure());
154 }
155 }
156}