Skip to content

Commit ae04479

Browse files
authored
Harden PKI: constrain CA, fail-hard secrets, cert TTL
* Fail hard when /dev/urandom is unavailable for secret generation * Constrain CA pathlen to 0 and replace Docker SANs with Apple Container * Set 365-day certificate validity instead of effectively-infinite
1 parent ba9eb8e commit ae04479

3 files changed

Lines changed: 21 additions & 15 deletions

File tree

crates/openshell-bootstrap/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ libc = "0.2"
1515
miette = { workspace = true }
1616
rcgen = { workspace = true }
1717
serde = { workspace = true }
18+
time = "0.3"
1819
serde_json = { workspace = true }
1920
tokio = { workspace = true }
2021
tracing = { workspace = true }

crates/openshell-bootstrap/src/pki.rs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use miette::{IntoDiagnostic, Result, WrapErr};
55
use rcgen::{BasicConstraints, CertificateParams, DnType, Ia5String, IsCa, KeyPair, SanType};
66
use std::net::IpAddr;
7+
use time::{Duration, OffsetDateTime};
78

89
/// All PEM-encoded materials produced by [`generate_pki`].
910
#[allow(clippy::struct_field_names)]
@@ -20,35 +21,35 @@ pub struct PkiBundle {
2021
/// Default SANs always included on the server certificate.
2122
const DEFAULT_SERVER_SANS: &[&str] = &[
2223
"openshell",
23-
"openshell.openshell.svc",
24-
"openshell.openshell.svc.cluster.local",
2524
"localhost",
26-
"host.docker.internal",
25+
"host.containers.internal",
2726
"127.0.0.1",
2827
];
2928

30-
/// SANs for the container bridge daemon certificate (macOS host).
31-
///
32-
/// `host.containers.internal` is the hostname Apple Container VMs use to reach
33-
/// the host, analogous to Docker's `host.docker.internal`.
29+
/// SANs for the server certificate. `host.containers.internal` is the hostname
30+
/// Apple Container VMs use to reach the macOS host.
3431
3532
/// Generate a complete PKI bundle: CA, server cert, and client cert.
3633
///
3734
/// `extra_sans` are additional Subject Alternative Names to add to the server
3835
/// certificate (e.g. the remote host's IP or hostname for remote deployments).
3936
///
40-
/// Certificate validity uses the `rcgen` defaults (1975–4096), which effectively
41-
/// never expire. This is appropriate for an internal dev-cluster PKI where certs
42-
/// are ephemeral to the cluster's lifetime.
37+
/// Certificates are valid for 365 days from generation. Regenerate the PKI
38+
/// bundle before expiry.
4339
pub fn generate_pki(extra_sans: &[String]) -> Result<PkiBundle> {
40+
let now = OffsetDateTime::now_utc();
41+
let expiry = now + Duration::days(365);
42+
4443
// --- CA ---
4544
let ca_key = KeyPair::generate()
4645
.into_diagnostic()
4746
.wrap_err("failed to generate CA key")?;
4847
let mut ca_params = CertificateParams::new(Vec::<String>::new())
4948
.into_diagnostic()
5049
.wrap_err("failed to create CA params")?;
51-
ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
50+
ca_params.is_ca = IsCa::Ca(BasicConstraints::Constrained(0));
51+
ca_params.not_before = now;
52+
ca_params.not_after = expiry;
5253
ca_params
5354
.distinguished_name
5455
.push(DnType::OrganizationName, "openshell");
@@ -70,6 +71,8 @@ pub fn generate_pki(extra_sans: &[String]) -> Result<PkiBundle> {
7071
.into_diagnostic()
7172
.wrap_err("failed to create server cert params")?;
7273
server_params.subject_alt_names = server_sans;
74+
server_params.not_before = now;
75+
server_params.not_after = expiry;
7376
server_params
7477
.distinguished_name
7578
.push(DnType::CommonName, "openshell-server");
@@ -86,6 +89,8 @@ pub fn generate_pki(extra_sans: &[String]) -> Result<PkiBundle> {
8689
let mut client_params = CertificateParams::new(Vec::<String>::new())
8790
.into_diagnostic()
8891
.wrap_err("failed to create client cert params")?;
92+
client_params.not_before = now;
93+
client_params.not_after = expiry;
8994
client_params
9095
.distinguished_name
9196
.push(DnType::CommonName, "openshell-client");

crates/openshell-bootstrap/src/runtime_apple.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ impl AppleContainerRuntime {
130130
let server_bin = find_server_binary()?;
131131

132132
// Generate SSH handshake secret.
133-
let secret = generate_secret();
133+
let secret = generate_secret()?;
134134

135135
// Build server arguments.
136136
let db_url = format!("sqlite://{}/openshell.db", data_dir.display());
@@ -489,11 +489,11 @@ fn find_server_binary() -> Result<PathBuf> {
489489
}
490490

491491
/// Generate a random hex secret for SSH handshake HMAC.
492-
fn generate_secret() -> String {
492+
fn generate_secret() -> Result<String> {
493493
use std::io::Read;
494494
let mut bytes = [0u8; 32];
495495
std::fs::File::open("/dev/urandom")
496496
.and_then(|mut f| f.read_exact(&mut bytes))
497-
.unwrap_or_default();
498-
bytes.iter().map(|b| format!("{b:02x}")).collect()
497+
.map_err(|e| miette::miette!("cannot read /dev/urandom for secret generation: {e}"))?;
498+
Ok(bytes.iter().map(|b| format!("{b:02x}")).collect())
499499
}

0 commit comments

Comments
 (0)