|
1 | | -use crate::structs::{Dsse, InTotoAttestation}; |
2 | 1 | use axum::{ |
3 | 2 | extract::Path, |
4 | | - http::{header, HeaderMap, StatusCode}, |
5 | | - response::{IntoResponse, Response}, |
| 3 | + http::{header, HeaderMap}, |
6 | 4 | }; |
7 | 5 | use axum_extra::response::ErasedJson; |
8 | | -use base64::prelude::*; |
9 | | -use lazy_static::lazy_static; |
10 | | -use regex::Regex; |
11 | | -use snafu::{ResultExt, Snafu}; |
12 | | -use std::process::Command; |
13 | | -use strum::{EnumDiscriminants, IntoStaticStr}; |
14 | | -use tracing::error; |
15 | 6 |
|
16 | | -lazy_static! { |
17 | | - static ref SHA256_REGEX: Regex = Regex::new(r"^[a-f0-9]{64}$").unwrap(); |
18 | | - static ref ALPHANUMERIC_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9\-]+$").unwrap(); |
19 | | -} |
20 | | - |
21 | | -#[derive(Snafu, Debug, EnumDiscriminants)] |
22 | | -#[strum_discriminants(derive(IntoStaticStr))] |
23 | | -#[snafu(visibility(pub))] |
24 | | -#[allow(clippy::enum_variant_names)] |
25 | | -pub enum DownloadSbomError { |
26 | | - #[snafu(display("invalid repository or digest"))] |
27 | | - InvalidSbomParameters, |
28 | | - #[snafu(display("failed to verify SBOM"))] |
29 | | - SbomVerification { |
30 | | - cosign_stdout: String, |
31 | | - cosign_stderr: String, |
32 | | - cosign_status: std::process::ExitStatus, |
33 | | - }, |
34 | | - #[snafu(display("cannot parse DSSE"))] |
35 | | - ParseDsse { source: serde_json::Error }, |
36 | | - #[snafu(display("cannot decode DSSE payload"))] |
37 | | - DecodeDssePayload { source: base64::DecodeError }, |
38 | | - #[snafu(display("cannot parse DSSE payload as string"))] |
39 | | - ParseDssePayloadAsString { source: std::str::Utf8Error }, |
40 | | - #[snafu(display("cannot parse in-toto attestation"))] |
41 | | - ParseInTotoAttestation { source: serde_json::Error }, |
42 | | - #[snafu(display("failed to execute cosign"))] |
43 | | - CosignExecution { source: std::io::Error }, |
44 | | -} |
45 | | - |
46 | | -impl IntoResponse for DownloadSbomError { |
47 | | - fn into_response(self) -> Response { |
48 | | - error!("error: {:?}", self); |
49 | | - ( |
50 | | - StatusCode::INTERNAL_SERVER_ERROR, |
51 | | - format!("Something went wrong: {}", self), |
52 | | - ) |
53 | | - .into_response() |
54 | | - } |
55 | | -} |
| 7 | +use crate::utils::{verify_attestation, DownloadSbomError}; |
56 | 8 |
|
57 | 9 | pub async fn download( |
58 | 10 | Path((repository, digest)): Path<(String, String)>, |
59 | 11 | ) -> Result<(HeaderMap, ErasedJson), DownloadSbomError> { |
60 | | - if !SHA256_REGEX.is_match(&digest) || !ALPHANUMERIC_REGEX.is_match(&repository) { |
61 | | - return Err(DownloadSbomError::InvalidSbomParameters); |
62 | | - } |
63 | | - let cmd_output = Command::new("cosign") |
64 | | - .arg("verify-attestation") |
65 | | - .arg("--type") |
66 | | - .arg("cyclonedx") |
67 | | - .arg("--certificate-identity-regexp") |
68 | | - .arg("^https://github.com/stackabletech/.+/.github/workflows/.+@.+") |
69 | | - .arg("--certificate-oidc-issuer") |
70 | | - .arg("https://token.actions.githubusercontent.com") |
71 | | - .arg(format!( |
72 | | - "oci.stackable.tech/sdp/{}@sha256:{}", |
73 | | - repository, digest |
74 | | - )) |
75 | | - .output() |
76 | | - .context(CosignExecutionSnafu)?; |
77 | | - |
78 | | - let output = String::from_utf8_lossy(&cmd_output.stdout); |
79 | | - if !cmd_output.status.success() { |
80 | | - let stderr_output = String::from_utf8_lossy(&cmd_output.stderr); |
81 | | - return Err(DownloadSbomError::SbomVerification { |
82 | | - cosign_stdout: output.to_string(), |
83 | | - cosign_stderr: stderr_output.to_string(), |
84 | | - cosign_status: cmd_output.status, |
85 | | - }); |
86 | | - } |
87 | | - |
88 | | - let dsse = serde_json::from_str::<Dsse>(&output).context(ParseDsseSnafu)?; |
89 | | - let attestation_bytes = BASE64_STANDARD |
90 | | - .decode(dsse.payload) |
91 | | - .context(DecodeDssePayloadSnafu)?; |
92 | | - let attestation_string = |
93 | | - std::str::from_utf8(&attestation_bytes).context(ParseDssePayloadAsStringSnafu)?; |
94 | | - let attestation = serde_json::from_str::<InTotoAttestation>(attestation_string) |
95 | | - .context(ParseInTotoAttestationSnafu)?; |
| 12 | + let attestation = verify_attestation(&repository, &digest).await?; |
96 | 13 | let mut headers = HeaderMap::new(); |
97 | 14 | headers.insert(header::CONTENT_TYPE, "application/json".parse().unwrap()); |
98 | 15 | headers.insert( |
|
0 commit comments