blue_build_process_management/drivers/
cosign_driver.rs1use std::{fmt::Debug, fs, path::Path};
2
3use blue_build_utils::{
4 constants::{COSIGN_PASSWORD, COSIGN_PUB_PATH, COSIGN_YES},
5 credentials::Credentials,
6};
7use colored::Colorize;
8use comlexr::{cmd, pipe};
9use log::{debug, trace};
10use miette::{bail, Context, IntoDiagnostic, Result};
11
12use crate::drivers::opts::VerifyType;
13
14use super::{
15 functions::get_private_key,
16 opts::{CheckKeyPairOpts, GenerateKeyPairOpts, SignOpts, VerifyOpts},
17 SigningDriver,
18};
19
20#[derive(Debug)]
21pub struct CosignDriver;
22
23impl SigningDriver for CosignDriver {
24 fn generate_key_pair(opts: &GenerateKeyPairOpts) -> Result<()> {
25 let path = opts.dir.as_ref().map_or_else(|| Path::new("."), |dir| dir);
26
27 let status = {
28 let c = cmd!(
29 cd path;
30 env {
31 COSIGN_PASSWORD: "",
32 COSIGN_YES: "true",
33 };
34 "cosign",
35 "generate-key-pair",
36 );
37 trace!("{c:?}");
38 c
39 }
40 .status()
41 .into_diagnostic()?;
42
43 if !status.success() {
44 bail!("Failed to generate cosign key-pair!");
45 }
46
47 Ok(())
48 }
49
50 fn check_signing_files(opts: &CheckKeyPairOpts) -> Result<()> {
51 let path = opts.dir.as_ref().map_or_else(|| Path::new("."), |dir| dir);
52 let priv_key = get_private_key(path)?;
53
54 let output = {
55 let c = cmd!(
56 env {
57 COSIGN_PASSWORD: "",
58 COSIGN_YES: "true"
59 };
60 "cosign",
61 "public-key",
62 format!("--key={priv_key}"),
63 );
64 trace!("{c:?}");
65 c
66 }
67 .output()
68 .into_diagnostic()?;
69
70 if !output.status.success() {
71 bail!(
72 "Failed to run cosign public-key: {}",
73 String::from_utf8_lossy(&output.stderr)
74 );
75 }
76
77 let calculated_pub_key = String::from_utf8(output.stdout).into_diagnostic()?;
78 let found_pub_key = fs::read_to_string(path.join(COSIGN_PUB_PATH))
79 .into_diagnostic()
80 .with_context(|| format!("Failed to read {COSIGN_PUB_PATH}"))?;
81 trace!("calculated_pub_key={calculated_pub_key},found_pub_key={found_pub_key}");
82
83 if calculated_pub_key.trim() == found_pub_key.trim() {
84 debug!("Cosign files match, continuing build");
85 Ok(())
86 } else {
87 bail!("Public key '{COSIGN_PUB_PATH}' does not match private key")
88 }
89 }
90
91 fn signing_login() -> Result<()> {
92 trace!("CosignDriver::signing_login()");
93
94 if let Some(Credentials {
95 registry,
96 username,
97 password,
98 }) = Credentials::get()
99 {
100 let output = pipe!(
101 stdin = password;
102 {
103 let c = cmd!(
104 "cosign",
105 "login",
106 "-u",
107 username,
108 "--password-stdin",
109 registry,
110 );
111 trace!("{c:?}");
112 c
113 }
114 )
115 .output()
116 .into_diagnostic()?;
117
118 if !output.status.success() {
119 let err_out = String::from_utf8_lossy(&output.stderr);
120 bail!("Failed to login for cosign:\n{}", err_out.trim());
121 }
122 debug!("Logged into {registry}");
123 }
124 Ok(())
125 }
126
127 fn sign(opts: &SignOpts) -> Result<()> {
128 if opts.image.digest().is_none() {
129 bail!(
130 "Image ref {} is not a digest ref",
131 opts.image.to_string().bold().red(),
132 );
133 }
134
135 let status = {
136 let c = cmd!(
137 env {
138 COSIGN_PASSWORD: "",
139 COSIGN_YES: "true",
140 };
141 "cosign",
142 "sign",
143 if let Some(ref key) = opts.key => format!("--key={key}"),
144 "--recursive",
145 opts.image.to_string(),
146 );
147 trace!("{c:?}");
148 c
149 }
150 .status()
151 .into_diagnostic()?;
152
153 if !status.success() {
154 bail!("Failed to sign {}", opts.image.to_string().bold().red());
155 }
156
157 Ok(())
158 }
159
160 fn verify(opts: &VerifyOpts) -> Result<()> {
161 let status = {
162 let c = cmd!(
163 "cosign",
164 "verify",
165 match &opts.verify_type {
166 VerifyType::File(path) => format!("--key={}", path.display()),
167 VerifyType::Keyless { issuer, identity } => [
168 "--certificate-identity-regexp",
169 &**identity,
170 "--certificate-oidc-issuer",
171 &**issuer,
172 ],
173 },
174 opts.image.to_string(),
175 );
176 trace!("{c:?}");
177 c
178 }
179 .status()
180 .into_diagnostic()?;
181
182 if !status.success() {
183 bail!("Failed to verify {}", opts.image.to_string().bold().red());
184 }
185
186 Ok(())
187 }
188}
189
190#[cfg(test)]
191mod test {
192 use std::{fs, path::Path};
193
194 use blue_build_utils::constants::{COSIGN_PRIV_PATH, COSIGN_PUB_PATH};
195 use tempfile::TempDir;
196
197 use crate::drivers::{
198 opts::{CheckKeyPairOpts, GenerateKeyPairOpts},
199 SigningDriver,
200 };
201
202 use super::CosignDriver;
203
204 #[test]
205 fn generate_key_pair() {
206 let tempdir = TempDir::new().unwrap();
207
208 let gen_opts = GenerateKeyPairOpts::builder().dir(tempdir.path()).build();
209
210 CosignDriver::generate_key_pair(&gen_opts).unwrap();
211
212 eprintln!(
213 "Private key:\n{}",
214 fs::read_to_string(tempdir.path().join(COSIGN_PRIV_PATH)).unwrap()
215 );
216 eprintln!(
217 "Public key:\n{}",
218 fs::read_to_string(tempdir.path().join(COSIGN_PUB_PATH)).unwrap()
219 );
220
221 let check_opts = CheckKeyPairOpts::builder().dir(tempdir.path()).build();
222
223 CosignDriver::check_signing_files(&check_opts).unwrap();
224 }
225
226 #[test]
227 fn check_key_pairs() {
228 let path = Path::new("../test-files/keys");
229
230 let opts = CheckKeyPairOpts::builder().dir(path).build();
231
232 CosignDriver::check_signing_files(&opts).unwrap();
233 }
234
235 #[test]
236 #[cfg(feature = "sigstore")]
237 fn compatibility() {
238 use crate::drivers::sigstore_driver::SigstoreDriver;
239
240 let tempdir = TempDir::new().unwrap();
241
242 let gen_opts = GenerateKeyPairOpts::builder().dir(tempdir.path()).build();
243
244 CosignDriver::generate_key_pair(&gen_opts).unwrap();
245
246 eprintln!(
247 "Private key:\n{}",
248 fs::read_to_string(tempdir.path().join(COSIGN_PRIV_PATH)).unwrap()
249 );
250 eprintln!(
251 "Public key:\n{}",
252 fs::read_to_string(tempdir.path().join(COSIGN_PUB_PATH)).unwrap()
253 );
254
255 let check_opts = CheckKeyPairOpts::builder().dir(tempdir.path()).build();
256
257 SigstoreDriver::check_signing_files(&check_opts).unwrap();
258 }
259}