Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 23 additions & 12 deletions src/commands/init/steps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -886,27 +886,23 @@ mod rollback_tests {
use std::fs;
use std::path::PathBuf;

use crate::commands::rotate::test_support::{
ScopedEnvVar, TEST_DOCKER_ARGS_ENV, env_lock, path_with_prepend,
write_fake_docker_script,
};
use super::test_support::write_self_contained_fake_docker;

let dir = tempfile::tempdir().unwrap();
let messages = crate::i18n::test_messages();
let bin_dir = dir.path().join("bin");
fs::create_dir_all(&bin_dir).unwrap();
write_fake_docker_script(&bin_dir.join("docker"));
// A fake that *would* log is what makes the absent log evidence:
// it is named through the seam, so anything the rollback ran
// would have left a record.
let fake = dir.path().join("fake-docker");
let args_log = dir.path().join("docker_args.log");
write_self_contained_fake_docker(&fake, &args_log);

let hcl_path = dir.path().join("openbao.hcl");
fs::write(&hcl_path, "tls_cert_file = ...\n").unwrap();
let runtime = tokio::runtime::Runtime::new().expect("tokio runtime");

let _lock = env_lock();
let _path = ScopedEnvVar::set("PATH", path_with_prepend(&bin_dir));
let _log = ScopedEnvVar::set(TEST_DOCKER_ARGS_ENV, &args_log);

let rollback = InitRollback {
docker: Some(fake),
hcl_backup: Some(RollbackFile {
path: hcl_path.clone(),
original: Some("tls_disable = 1\n".to_string()),
Expand Down Expand Up @@ -987,14 +983,29 @@ pub(super) mod test_support {
pub(in crate::commands::init::steps) fn write_self_contained_fake_docker(
path: &Path,
args_log: &Path,
) {
write_self_contained_fake_docker_exiting(path, args_log, 0);
}

/// [`write_self_contained_fake_docker`] whose every invocation exits
/// `exit_code` after logging, so a test can steer the failure path
/// of a docker call production spawns on its behalf.
///
/// The log it writes is the same one the zero-exit writer produces —
/// one appended, space-joined line per invocation — so a test that
/// swaps one writer for the other keeps its assertions.
pub(in crate::commands::init::steps) fn write_self_contained_fake_docker_exiting(
path: &Path,
args_log: &Path,
exit_code: u8,
) {
let log = args_log.display().to_string();
assert!(
!log.contains('\''),
"the log path is interpolated into a single-quoted shell word"
);
let script = format!(
"#!/bin/sh\nset -eu\n{{ printf '%s ' \"$@\"; printf '\\n'; }} >> '{log}'\nexit 0\n"
"#!/bin/sh\nset -eu\n{{ printf '%s ' \"$@\"; printf '\\n'; }} >> '{log}'\nexit {exit_code}\n"
);
fs::write(path, script).expect("fake docker script should be written");
fs::set_permissions(path, fs::Permissions::from_mode(0o700))
Expand Down
27 changes: 6 additions & 21 deletions src/commands/init/steps/http01_admin_tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ pub(crate) fn strip_responder_tls_config(secrets_dir: &Path, messages: &Messages
mod tests {
use std::collections::BTreeMap;

use super::super::test_support::write_self_contained_fake_docker;
use super::*;
/// The responder container name a default install renders.
const DEFAULT_RESPONDER_CONTAINER: &str = "bootroot-http01";
Expand Down Expand Up @@ -301,9 +302,9 @@ mod tests {
#[test]
fn reissue_falls_back_to_instance_scoped_sans() {
let dir = tempfile::tempdir().unwrap();
let bin_dir = dir.path().join("bin");
std::fs::create_dir(&bin_dir).unwrap();
crate::commands::rotate::test_support::write_fake_docker_script(&bin_dir.join("docker"));
let fake = dir.path().join("fake-docker");
let args_log = dir.path().join("docker_args.log");
write_self_contained_fake_docker(&fake, &args_log);

let secrets_dir = dir.path().join("secrets");
let tls_dir = secrets_dir.join("bootroot-http01").join("tls");
Expand All @@ -324,26 +325,10 @@ mod tests {
expires_at: None,
};

let args_log = dir.path().join("docker_args.log");
let messages = crate::i18n::test_messages();
let _lock = crate::commands::rotate::test_support::env_lock();
let _path = crate::commands::rotate::test_support::ScopedEnvVar::set(
"PATH",
crate::commands::rotate::test_support::path_with_prepend(&bin_dir),
);
let _log = crate::commands::rotate::test_support::ScopedEnvVar::set(
crate::commands::rotate::test_support::TEST_DOCKER_ARGS_ENV,
&args_log,
);

reissue_http01_admin_tls_cert(
&secrets_dir,
&entry,
"insight-http01",
Path::new("docker"),
&messages,
)
.expect("re-issuance must succeed against the fake docker");
reissue_http01_admin_tls_cert(&secrets_dir, &entry, "insight-http01", &fake, &messages)
.expect("re-issuance must succeed against the fake docker");

let log = std::fs::read_to_string(&args_log).unwrap_or_default();
assert!(
Expand Down
80 changes: 22 additions & 58 deletions src/commands/init/steps/openbao_tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,34 +475,14 @@ mod tests {
use std::collections::BTreeMap;
use std::fs;

use super::super::test_support::write_self_contained_fake_docker;
use super::*;
use crate::commands::rotate::test_support::{
ScopedEnvVar, TEST_DOCKER_ARGS_ENV, env_lock, path_with_prepend,
use super::super::test_support::{
write_self_contained_fake_docker, write_self_contained_fake_docker_exiting,
};
use super::*;

/// The `OpenBao` container name a default install renders.
const DEFAULT_OPENBAO_CONTAINER: &str = "bootroot-openbao";

/// Fake `docker` that *appends* one line per invocation, unlike the
/// shared helper which truncates: the ordering of the two containers
/// this file runs is exactly what the wiring test below pins.
///
/// `exit_code` is returned by every invocation, so a non-zero value
/// makes the first container this file runs — the chown — fail.
fn write_appending_fake_docker(path: &Path, exit_code: u8) {
let script = format!(
r#"#!/bin/sh
set -eu
{{ printf '%s ' "$@"; printf '\n'; }} >> "${{BOOTROOT_TEST_DOCKER_ARGS:?missing log path}}"
exit {exit_code}
"#
);
fs::write(path, script).expect("fake docker script should be written");
fs::set_permissions(path, std::fs::Permissions::from_mode(0o700))
.expect("fake docker script should be executable");
}

#[test]
fn build_sans_includes_specific_ip() {
let sans = build_openbao_tls_sans("192.168.1.10:8200", None, DEFAULT_OPENBAO_CONTAINER);
Expand Down Expand Up @@ -532,9 +512,9 @@ exit {exit_code}
#[test]
fn reissue_falls_back_to_instance_scoped_sans() {
let dir = tempfile::tempdir().unwrap();
let bin_dir = dir.path().join("bin");
fs::create_dir(&bin_dir).unwrap();
write_appending_fake_docker(&bin_dir.join("docker"), 0);
let fake = dir.path().join("fake-docker");
let args_log = dir.path().join("docker_args.log");
write_self_contained_fake_docker(&fake, &args_log);

let compose_dir = dir.path().join("compose");
let secrets_dir = dir.path().join("secrets");
Expand All @@ -557,18 +537,14 @@ exit {exit_code}
expires_at: None,
};

let args_log = dir.path().join("docker_args.log");
let messages = crate::i18n::test_messages();
let _lock = env_lock();
let _path = ScopedEnvVar::set("PATH", path_with_prepend(&bin_dir));
let _log = ScopedEnvVar::set(TEST_DOCKER_ARGS_ENV, &args_log);

reissue_openbao_tls_cert(
&compose_dir,
&secrets_dir,
&entry,
"insight-openbao",
Path::new("docker"),
&fake,
&messages,
)
.expect("re-issuance must succeed against the fake docker");
Expand Down Expand Up @@ -860,9 +836,9 @@ exit {exit_code}
#[test]
fn issue_openbao_tls_cert_chowns_output_dir_before_creating_the_cert() {
let dir = tempfile::tempdir().unwrap();
let bin_dir = dir.path().join("bin");
fs::create_dir(&bin_dir).unwrap();
write_appending_fake_docker(&bin_dir.join("docker"), 0);
let fake = dir.path().join("fake-docker");
let args_log = dir.path().join("docker_args.log");
write_self_contained_fake_docker(&fake, &args_log);

let compose_dir = dir.path().join("compose");
let secrets_dir = dir.path().join("secrets");
Expand All @@ -874,18 +850,13 @@ exit {exit_code}
fs::write(tls_dir.join("server.crt"), "cert").unwrap();
fs::write(tls_dir.join("server.key"), "key").unwrap();

let args_log = dir.path().join("docker_args.log");
let messages = crate::i18n::test_messages();

let _lock = env_lock();
let _path = ScopedEnvVar::set("PATH", path_with_prepend(&bin_dir));
let _log = ScopedEnvVar::set(TEST_DOCKER_ARGS_ENV, &args_log);

issue_openbao_tls_cert(
&compose_dir,
&secrets_dir,
&["openbao.internal"],
Path::new("docker"),
&fake,
&messages,
)
.expect("issuing the certificate must succeed against the fake docker");
Expand Down Expand Up @@ -936,26 +907,21 @@ exit {exit_code}
#[test]
fn issue_openbao_tls_cert_aborts_when_the_chown_fails() {
let dir = tempfile::tempdir().unwrap();
let bin_dir = dir.path().join("bin");
fs::create_dir(&bin_dir).unwrap();
write_appending_fake_docker(&bin_dir.join("docker"), 1);
let fake = dir.path().join("fake-docker");
let args_log = dir.path().join("docker_args.log");
write_self_contained_fake_docker_exiting(&fake, &args_log, 1);

let compose_dir = dir.path().join("compose");
let secrets_dir = dir.path().join("secrets");
fs::create_dir_all(&secrets_dir).unwrap();

let args_log = dir.path().join("docker_args.log");
let messages = crate::i18n::test_messages();

let _lock = env_lock();
let _path = ScopedEnvVar::set("PATH", path_with_prepend(&bin_dir));
let _log = ScopedEnvVar::set(TEST_DOCKER_ARGS_ENV, &args_log);

let error = issue_openbao_tls_cert(
&compose_dir,
&secrets_dir,
&["openbao.internal"],
Path::new("docker"),
&fake,
&messages,
)
.expect_err("a failing chown must fail the issuance");
Expand Down Expand Up @@ -988,9 +954,12 @@ exit {exit_code}
#[test]
fn issue_openbao_tls_cert_refuses_a_symlinked_output_dir() {
let dir = tempfile::tempdir().unwrap();
let bin_dir = dir.path().join("bin");
fs::create_dir(&bin_dir).unwrap();
write_appending_fake_docker(&bin_dir.join("docker"), 0);
// The fake would log any invocation it received, so the empty
// log below is evidence that nothing ran rather than evidence
// that nothing could.
let fake = dir.path().join("fake-docker");
let args_log = dir.path().join("docker_args.log");
write_self_contained_fake_docker(&fake, &args_log);

let compose_dir = dir.path().join("compose");
let secrets_dir = dir.path().join("secrets");
Expand All @@ -1003,18 +972,13 @@ exit {exit_code}
fs::create_dir_all(tls_dir.parent().expect("openbao dir")).unwrap();
std::os::unix::fs::symlink(&elsewhere, &tls_dir).unwrap();

let args_log = dir.path().join("docker_args.log");
let messages = crate::i18n::test_messages();

let _lock = env_lock();
let _path = ScopedEnvVar::set("PATH", path_with_prepend(&bin_dir));
let _log = ScopedEnvVar::set(TEST_DOCKER_ARGS_ENV, &args_log);

let error = issue_openbao_tls_cert(
&compose_dir,
&secrets_dir,
&["openbao.internal"],
Path::new("docker"),
&fake,
&messages,
)
.expect_err("a symlinked output directory must fail the issuance");
Expand Down
55 changes: 21 additions & 34 deletions src/commands/init/steps/openbao_transition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,10 +350,8 @@ mod tests {
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};

use super::super::test_support::write_self_contained_fake_docker;
use super::*;
use crate::commands::rotate::test_support::{
ScopedEnvVar, TEST_DOCKER_ARGS_ENV, env_lock, path_with_prepend, write_fake_docker_script,
};
use crate::i18n::test_messages;

const PROBE_ATTEMPTS: u32 = 1;
Expand Down Expand Up @@ -577,37 +575,32 @@ mod tests {
/// The availability pre-check runs before Docker is touched, so a
/// deployment is never knocked into a sealed state `init` cannot
/// recover from.
#[test]
fn no_available_source_fails_before_any_docker_call() {
#[tokio::test]
async fn no_available_source_fails_before_any_docker_call() {
let dir = tempdir().expect("temp dir");
let bin_dir = dir.path().join("bin");
fs::create_dir_all(&bin_dir).expect("bin dir");
write_fake_docker_script(&bin_dir.join("docker"));
// The transition is handed a fake that logs every invocation, so
// the absent log below proves no docker command was emitted.
let fake = dir.path().join("fake-docker");
let args_log = dir.path().join("docker_args.log");
write_self_contained_fake_docker(&fake, &args_log);
let compose = dir.path().join("docker-compose.yml");
let override_path = dir.path().join("docker-compose.openbao-exposed.yml");
let default_file = dir.path().join("unseal-keys.txt");
let runtime = tokio::runtime::Runtime::new().expect("tokio runtime");

let _lock = env_lock();
let _path = ScopedEnvVar::set("PATH", path_with_prepend(&bin_dir));
let _log = ScopedEnvVar::set(TEST_DOCKER_ARGS_ENV, &args_log);

let transition = OpenBaoTlsTransition {
docker: &fake,
probe_attempts: PROBE_ATTEMPTS,
probe_delay: PROBE_DELAY,
..OpenBaoTlsTransition::new(&compose, &override_path, UNREACHABLE_HTTPS_URL, dir.path())
};
let mut recreated = false;
// `block_on` rather than `#[tokio::test]`: the environment lock
// has to stay held across the whole run so a parallel test
// cannot swap PATH out from under the fake `docker`.
let err = runtime
.block_on(transition.run(
let err = transition
.run(
&inputs(&[], None, default_file, false),
&mut recreated,
&test_messages(),
))
)
.await
.expect_err("no unseal key source is available");

assert!(
Expand Down Expand Up @@ -785,39 +778,33 @@ mod tests {
/// same failure a listener still answering plaintext produces.
/// `state.openbao_url` is advanced by the caller only after this
/// returns `Ok`, so the pre-TLS plaintext URL survives.
#[test]
fn tls_probe_failure_stops_before_the_url_is_recorded() {
#[tokio::test]
async fn tls_probe_failure_stops_before_the_url_is_recorded() {
let dir = tempdir().expect("temp dir");
let bin_dir = dir.path().join("bin");
fs::create_dir_all(&bin_dir).expect("bin dir");
write_fake_docker_script(&bin_dir.join("docker"));
let fake = dir.path().join("fake-docker");
let args_log = dir.path().join("docker_args.log");
write_self_contained_fake_docker(&fake, &args_log);
let compose = dir.path().join("docker-compose.yml");
let override_path = dir.path().join("docker-compose.openbao-exposed.yml");
let state_path = dir.path().join("state.json");
let plaintext_state = "{\"openbao_url\":\"http://127.0.0.1:8200\"}\n";
fs::write(&state_path, plaintext_state).expect("write state");
let in_memory = vec!["memory-key".to_string()];
let runtime = tokio::runtime::Runtime::new().expect("tokio runtime");

let _lock = env_lock();
let _path = ScopedEnvVar::set("PATH", path_with_prepend(&bin_dir));
let _log = ScopedEnvVar::set(TEST_DOCKER_ARGS_ENV, &args_log);

let transition = OpenBaoTlsTransition {
docker: &fake,
probe_attempts: PROBE_ATTEMPTS,
probe_delay: PROBE_DELAY,
..OpenBaoTlsTransition::new(&compose, &override_path, UNREACHABLE_HTTPS_URL, dir.path())
};
let mut recreated = false;
// See `no_available_source_fails_before_any_docker_call` for why
// this drives the future with `block_on`.
let err = runtime
.block_on(transition.run(
let err = transition
.run(
&inputs(&in_memory, None, dir.path().join("unseal-keys.txt"), false),
&mut recreated,
&test_messages(),
))
)
.await
.expect_err("nothing is listening on the probed URL");

assert!(
Expand Down
Loading
Loading