Skip to content

Commit 1a8ce0e

Browse files
authored
Update Caddy to 2.11.1 and verify download checksums (#78)
* Update to next minor version of Caddy web server https://github.com/caddyserver/caddy/releases/tag/v2.11.1 * Verify checksum of Caddy binary downloads * Clarify Caddy checksum errors
1 parent 314abca commit 1a8ce0e

File tree

5 files changed

+207
-5
lines changed

5 files changed

+207
-5
lines changed

Cargo.lock

Lines changed: 72 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

buildpacks/static-web-server/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ bullet_stream = "0.10.0"
2323
tracing = "0.1.44"
2424
const_format = "0.2.35"
2525
glob = "0.3.3"
26+
sha2 = "0.10"
2627

2728
[dev-dependencies]
2829
libcnb-test = "=0.30.2"

buildpacks/static-web-server/src/errors.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ pub(crate) enum StaticWebServerBuildpackError {
2424
CannotCreateWebExecD(std::io::Error),
2525
CannotInstallEnvAsHtmlData(std::io::Error),
2626
ConfigurationConstraint(String),
27+
ChecksumVerificationFailed(String),
28+
CannotReadChecksums {
29+
filename: String,
30+
error: std::io::Error,
31+
},
2732
}
2833

2934
pub(crate) struct ErrorMessage {
@@ -58,11 +63,12 @@ pub(crate) fn on_error(error: libcnb::Error<StaticWebServerBuildpackError>) {
5863
eprintln!();
5964
}
6065

66+
#[allow(clippy::too_many_lines)]
6167
fn buildpack_error_message(error: StaticWebServerBuildpackError) -> ErrorMessage {
6268
match error {
6369
StaticWebServerBuildpackError::Download(e) => ErrorMessage {
6470
message: formatdoc! {"
65-
Unable to download the static web server for {buildpack_name}.
71+
Failed to download Caddy web server for {buildpack_name}.
6672
", buildpack_name = style::value(BUILDPACK_NAME) },
6773
error_string: e.to_string(),
6874
error_id: "download_error".to_string(),
@@ -144,6 +150,23 @@ fn buildpack_error_message(error: StaticWebServerBuildpackError) -> ErrorMessage
144150
error_string: e,
145151
error_id: "configuration_constraint_error".to_string(),
146152
},
153+
StaticWebServerBuildpackError::ChecksumVerificationFailed(e) => ErrorMessage {
154+
message: formatdoc! {"
155+
Failed to verify Caddy checksum for {buildpack_name}
156+
157+
The downloaded Caddy binary's checksum does not match the expected value.
158+
This could indicate a corrupted download or network tampering.
159+
", buildpack_name = style::value(BUILDPACK_NAME) },
160+
error_string: e,
161+
error_id: "checksum_verification_failed_error".to_string(),
162+
},
163+
StaticWebServerBuildpackError::CannotReadChecksums { filename, error } => ErrorMessage {
164+
message: formatdoc! {"
165+
Failed to verify Caddy checksum, reading {filename}, for {buildpack_name}
166+
", buildpack_name = style::value(BUILDPACK_NAME), filename = style::value(filename) },
167+
error_string: error.to_string(),
168+
error_id: "cannot_read_checksums_error".to_string(),
169+
},
147170
}
148171
}
149172

buildpacks/static-web-server/src/install_web_server.rs

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use std::fs;
2+
use std::io::{BufRead, BufReader};
3+
use std::path::Path;
24

35
use libcnb::build::BuildContext;
46
use libcnb::data::layer_name;
@@ -10,6 +12,7 @@ use libherokubuildpack::download::download_file;
1012
use libherokubuildpack::log::log_info;
1113
use libherokubuildpack::tar::decompress_tarball;
1214
use serde::{Deserialize, Serialize};
15+
use sha2::{Digest, Sha512};
1316
use tempfile::NamedTempFile;
1417

1518
use crate::o11y::*;
@@ -79,8 +82,18 @@ pub(crate) fn install_web_server(
7982
{ INSTALLATION_WEB_SERVER_VERSION } = web_server_version,
8083
"downloading web server"
8184
);
82-
download_file(artifact_url, web_server_tgz.path())
85+
download_file(&artifact_url, web_server_tgz.path())
8386
.map_err(StaticWebServerBuildpackError::Download)?;
87+
88+
// Verify the checksum
89+
log_info("Verifying web server checksum");
90+
verify_caddy_checksum(
91+
web_server_version,
92+
&context.target.os,
93+
&context.target.arch,
94+
web_server_tgz.path(),
95+
)?;
96+
8497
decompress_tarball(&mut web_server_tgz.into_file(), &web_server_dir)
8598
.map_err(StaticWebServerBuildpackError::CannotUnpackCaddyTarball)?;
8699
}
@@ -117,3 +130,98 @@ pub(crate) struct WebServerLayerMetadata {
117130
arch: String,
118131
os: String,
119132
}
133+
134+
/// Verifies the Caddy binary checksum against the official checksums file
135+
fn verify_caddy_checksum(
136+
version: &str,
137+
os: &str,
138+
arch: &str,
139+
tarball_path: &Path,
140+
) -> Result<(), libcnb::Error<StaticWebServerBuildpackError>> {
141+
let base_url = format!("https://github.com/caddyserver/caddy/releases/download/v{version}");
142+
let checksums_filename = format!("caddy_{version}_checksums.txt");
143+
let artifact_filename = format!("caddy_{version}_{os}_{arch}.tar.gz");
144+
145+
// Download checksums file
146+
let checksums_file = NamedTempFile::new()
147+
.map_err(StaticWebServerBuildpackError::CannotCreateCaddyTarballFile)?;
148+
download_file(
149+
format!("{base_url}/{checksums_filename}"),
150+
checksums_file.path(),
151+
)
152+
.map_err(StaticWebServerBuildpackError::Download)?;
153+
154+
// Verify the tarball checksum against the checksums file
155+
verify_checksum(tarball_path, checksums_file.path(), &artifact_filename)?;
156+
157+
tracing::info!("Successfully verified Caddy checksum for version {version}");
158+
159+
Ok(())
160+
}
161+
162+
/// Verifies the checksum of a file against a checksums file
163+
fn verify_checksum(
164+
file_path: &Path,
165+
checksums_path: &Path,
166+
expected_filename: &str,
167+
) -> Result<(), libcnb::Error<StaticWebServerBuildpackError>> {
168+
// Calculate the SHA512 hash of the downloaded file
169+
let mut file = fs::File::open(file_path).map_err(|error| {
170+
StaticWebServerBuildpackError::CannotReadChecksums {
171+
filename: expected_filename.to_string(),
172+
error,
173+
}
174+
})?;
175+
let mut hasher = Sha512::new();
176+
std::io::copy(&mut file, &mut hasher).map_err(|error| {
177+
StaticWebServerBuildpackError::CannotReadChecksums {
178+
filename: expected_filename.to_string(),
179+
error,
180+
}
181+
})?;
182+
let calculated_hash = format!("{:x}", hasher.finalize());
183+
184+
// Parse the checksums file to find the expected checksum
185+
let checksums_filename = checksums_path
186+
.file_name()
187+
.and_then(|n| n.to_str())
188+
.unwrap_or("checksums.txt");
189+
let checksums_file = fs::File::open(checksums_path).map_err(|error| {
190+
StaticWebServerBuildpackError::CannotReadChecksums {
191+
filename: checksums_filename.to_string(),
192+
error,
193+
}
194+
})?;
195+
let reader = BufReader::new(checksums_file);
196+
197+
let mut found_checksum = None;
198+
for line in reader.lines() {
199+
let line = line.map_err(|error| StaticWebServerBuildpackError::CannotReadChecksums {
200+
filename: checksums_filename.to_string(),
201+
error,
202+
})?;
203+
let parts: Vec<&str> = line.split_whitespace().collect();
204+
if parts.len() >= 2 && parts[1] == expected_filename {
205+
found_checksum = Some(parts[0].to_string());
206+
break;
207+
}
208+
}
209+
210+
let expected_hash = found_checksum.ok_or_else(|| {
211+
StaticWebServerBuildpackError::ChecksumVerificationFailed(format!(
212+
"Checksum for {expected_filename} not found in checksums file"
213+
))
214+
})?;
215+
216+
// Compare checksums
217+
if calculated_hash != expected_hash {
218+
return Err(
219+
StaticWebServerBuildpackError::ChecksumVerificationFailed(format!(
220+
"Checksum mismatch for {expected_filename}: expected {expected_hash}, got {calculated_hash}"
221+
))
222+
.into(),
223+
);
224+
}
225+
226+
Ok(())
227+
}

buildpacks/static-web-server/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use ureq as _;
3232
const BUILDPACK_NAME: &str = "Heroku Static Web Server Buildpack";
3333
const BUILD_PLAN_ID: &str = "static-web-server";
3434
pub(crate) const WEB_SERVER_NAME: &str = "caddy";
35-
pub(crate) const WEB_SERVER_VERSION: &str = "2.10.2";
35+
pub(crate) const WEB_SERVER_VERSION: &str = "2.11.1";
3636

3737
pub(crate) struct StaticWebServerBuildpack;
3838

0 commit comments

Comments
 (0)