Skip to content
Open
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
10 changes: 10 additions & 0 deletions .agents/skills/watch-github-actions/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,3 +331,13 @@ gh run rerun <run-id>
```bash
gh run rerun <run-id> --failed
```

## Crypto backend independence

For failures in Branch Checks' library crypto selection step (Linux x86_64), run
`mise run rust:verify:library-crypto` from the checkout. It checks external library
consumer graphs and TLS/JWT behavior separately from workspace tests, then checks
executable backend defaults across all targets. It also exercises a system-OpenSSL
consumer; use `nix develop .#crypto` for the required libraries. Do not add AWS-LC
to a shared library's defaults to make a test
pass; standalone library tests select their backend in development dependencies.
5 changes: 5 additions & 0 deletions .github/workflows/branch-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,11 @@ jobs:
fi
cargo test --locked -p openshell-crypto --no-default-features --test independent_backend

- name: Verify library crypto selection
if: matrix.system == 'x86_64-linux'
shell: nix develop .#crypto -c bash -euo pipefail {0}
run: mise run rust:verify:library-crypto

- name: Verify OpenSSL backend
shell: nix develop .#crypto -c bash -euo pipefail {0}
env:
Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ license = "Apache-2.0"
repository = "https://github.com/NVIDIA/OpenShell"

[workspace.dependencies]
# Product backend selection. Consumer crates inherit this choice.
openshell-crypto = { path = "crates/openshell-crypto", default-features = false, features = ["aws-lc"] }
# Libraries inherit only the facade. Executable packages select their backend.
openshell-crypto = { path = "crates/openshell-crypto", default-features = false }
# Async runtime
tokio = { version = "1.53.3", features = ["full"] }

Expand Down Expand Up @@ -73,7 +73,7 @@ tracing-appender = "0.2"
opentelemetry-proto = { version = "0.32", default-features = false, features = ["gen-tonic", "trace"] }
opentelemetry = "0.32"
opentelemetry_sdk = { version = "0.32", features = ["rt-tokio"] }
opentelemetry-otlp = { version = "0.32", default-features = false, features = ["grpc-tonic", "gzip-tonic", "tls-aws-lc", "tls-roots", "trace"] }
opentelemetry-otlp = { version = "0.32", default-features = false, features = ["grpc-tonic", "gzip-tonic", "tls-provider-agnostic", "tls-roots", "trace"] }
tracing-opentelemetry = { version = "0.33", default-features = false, features = ["tracing-log"] }

# Metrics
Expand Down
3 changes: 3 additions & 0 deletions crates/openshell-binary-identity/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ openshell-crypto = { workspace = true }

[lints]
workspace = true

[dev-dependencies]
openshell-crypto = { workspace = true, features = ["aws-lc"] }
1 change: 1 addition & 0 deletions crates/openshell-bootstrap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ tokio = { workspace = true }
tracing = { workspace = true }

[dev-dependencies]
openshell-crypto = { workspace = true, features = ["aws-lc"] }
x509-parser = "0.16"

[lints]
Expand Down
2 changes: 1 addition & 1 deletion crates/openshell-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ name = "openshell"
path = "src/main.rs"

[dependencies]
openshell-crypto = { workspace = true }
openshell-crypto = { workspace = true, features = ["aws-lc"] }
openshell-bootstrap = { path = "../openshell-bootstrap" }
openshell-core = { path = "../openshell-core", default-features = false }
openshell-policy = { path = "../openshell-policy" }
Expand Down
3 changes: 3 additions & 0 deletions crates/openshell-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ rustix = { workspace = true }
libc = "0.2"

[features]
# Application opt-in; libraries remain backend-neutral by default.
aws-lc = ["openshell-crypto/aws-lc"]
default = ["telemetry"]
## Compile in anonymous telemetry emission support. On by default; disable with
## `--no-default-features` (plus any other features you need) for a build that
Expand All @@ -66,6 +68,7 @@ tonic-prost-build = { workspace = true }
protoc-bin-vendored = { workspace = true }

[dev-dependencies]
openshell-crypto = { workspace = true, features = ["aws-lc"] }
h2 = "0.4"
rcgen = { workspace = true }
tempfile = "3"
Expand Down
1 change: 1 addition & 0 deletions crates/openshell-core/src/grpc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ fn configure_gateway_tls(
ca_pem: Vec<u8>,
server_name: Option<&str>,
) -> Result<Endpoint> {
openshell_crypto::tls::try_ensure_default_provider().into_diagnostic()?;
let mut tls_config = ClientTlsConfig::new().ca_certificate(Certificate::from_pem(ca_pem));
if let Some(server_name) = server_name.filter(|name| !name.is_empty()) {
tls_config = tls_config.domain_name(server_name);
Expand Down
10 changes: 10 additions & 0 deletions crates/openshell-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@
//! - Configuration management
//! - Common error types
//! - Build version metadata
//!
//! # Cryptography
//!
//! This library is backend-neutral by default. Enable its `aws-lc` feature, or
//! add `openshell-crypto` with defaults disabled and install your own
//! `CryptoContext` before constructing TLS/JWT clients or using crypto.
//! Existing Rustls process providers are preserved by dependency-owned clients;
//! selecting a context does not replace an already installed process provider.
//! If no provider is installed, the first TLS use installs the facade provider
//! process-wide. Install any host provider before that first use.

pub mod activity;
pub mod auth;
Expand Down
10 changes: 6 additions & 4 deletions crates/openshell-crypto/CRYPTO.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Cryptography and FIPS

OpenShell routes first-party security-sensitive cryptography through
`openshell-crypto`. The workspace selects AWS-LC as the production backend.
`openshell-crypto`. Executable packages select AWS-LC as the production backend.
The opt-in `openssl` feature supplies a system-OpenSSL backend for explicit
context selection. Neither backend asserts deployment FIPS compliance, and strict
posture requests fail closed. Application startup, dependency-owned transports,
Expand Down Expand Up @@ -29,9 +29,11 @@ remain compatibility constraints when adding or changing a backend.

## Selection and initialization

Consumer crates inherit the workspace's backend choice and enable integration
features on the facade. A build without the default backend must install a
context before facade use. Select the context before constructing clients,
Reusable libraries inherit a backend-neutral workspace dependency and enable
integration features on the facade. Executable packages select AWS-LC explicitly;
standalone library tests select it through development dependencies. An embedder
must select a backend and install its context before facade use. Select the
context before constructing clients,
listeners, or dependencies that initialize process-wide crypto providers.

Explicit context selection controls first-party TLS builders even when a
Expand Down
43 changes: 35 additions & 8 deletions crates/openshell-crypto/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,33 @@ depending on rcgen's compiled crypto implementation.

## Build selection

The crate's default `aws-lc` feature selects the current implementation. The root
workspace declares that choice once in its `openshell-crypto` dependency;
application crates inherit it rather than choosing a backend independently.
Standalone examples select their own default through their path dependency.
Disabling crate defaults removes its AWS-LC implementation and requires an
explicit context before use; use without initialization fails closed with an
initialization panic. This mode is for embedders and backend contract tests, not
an independently runnable OpenShell product configuration.
The crate's default `aws-lc` feature selects the current implementation. The
workspace dependency disables defaults and selects no backend. Reusable libraries
including `openshell-core`, `openshell-sdk`, and `openshell-policy` therefore do
not enable AWS-LC, including through core's optional telemetry and tracing features.
Their development dependencies select AWS-LC only for standalone crate tests.

Executable packages explicitly enable `openshell-crypto/aws-lc`, including builds
with package defaults disabled, except the VM guest init package mode, which
does not use cryptography. This preserves shipped behavior. Cargo features
apply to a whole package: the library targets in CLI, gateway, supervisor, sandbox,
and standalone driver packages also carry that product choice. Embed through the
backend-neutral core/SDK/policy crates when choosing your own implementation.
Standalone examples and test harnesses select their backend explicitly.

An embedder can depend on `openshell-crypto` with `default-features = false`,
implement `CryptoBackend` and `ProtocolBackend`, and call
`install_default_context(CryptoContext::new(Box::new(backend)))` before constructing
clients or performing cryptography. Merely installing a Rustls provider does not
supply primitive, PKI, or JWT operations. To retain OpenShell's AWS-LC behavior,
enable the opt-in `aws-lc` feature on `openshell-core` or `openshell-sdk`, or add
`openshell-crypto` with its default features enabled. SDK connections and fallible
TLS builders return initialization errors when no provider is available. Legacy
infallible helpers still panic; prefer `try_default_context`,
`tls::try_ensure_default_provider`, and `openshell_sdk::oidc::try_http_client`.
If a host has not installed a Rustls provider, the first dependency-owned TLS
client installs the facade provider process-wide. Install a host provider first
when it must own that global.

Integration features (`tls-tonic`, `tls-kube`, `tls-sqlx`) activate
dependencies. The `aws-lc` feature supplies their AWS-LC choices conditionally.
Expand Down Expand Up @@ -321,3 +340,11 @@ not target RHEL qualification or product E2E tests.
Existing bootstrap, credential-store, gateway TLS/OIDC, and proxy tests exercise the
migrated consumers. The cargo-deny Ring restrictions and their explicit wrapper
exceptions remain in force.

`mise run rust:verify:library-crypto` checks isolated consumer graphs for core,
SDK, and policy, then runs TLS and JWT probes with consumer-owned Ring/RustCrypto
implementations. The fixture is outside the product workspace, so product and
dev-dependency feature unification cannot hide regressions. Ring here is a test
consumer choice, not a new production backend or a relaxation of `deny.toml`.
The same check verifies AWS-LC remains selected by executable packages both with
and without their default features.
21 changes: 16 additions & 5 deletions crates/openshell-crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ use std::sync::{Arc, OnceLock};
/// Errors contain no key material or authentication-failure details.
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum CryptoError {
/// No backend was selected by the application.
#[error(
"enable the aws-lc feature on openshell-sdk, openshell-core, or openshell-crypto, or call install_default_context before crypto use"
)]
MissingContext,
/// A backend could not obtain cryptographic randomness.
#[error("cryptographic random generation failed")]
Random,
Expand Down Expand Up @@ -180,21 +185,27 @@ pub(crate) fn explicitly_selected() -> bool {
/// Application default, initialized lazily to AWS-LC unless selected before first use.
#[must_use]
pub fn default_context() -> &'static CryptoContext {
try_default_context()
.expect("enable openshell-crypto/aws-lc or call install_default_context before crypto use")
}

/// Obtain the application context, failing when no backend has been selected.
pub fn try_default_context() -> Result<&'static CryptoContext, CryptoError> {
#[cfg(feature = "aws-lc")]
{
&DEFAULT
Ok(&DEFAULT
.get_or_init(|| DefaultContext {
context: CryptoContext::default(),
explicit: false,
})
.context
.context)
}
#[cfg(not(feature = "aws-lc"))]
{
&DEFAULT
DEFAULT
.get()
.expect("install a crypto context before use when no default backend is built")
.context
.map(|selected| &selected.context)
.ok_or(CryptoError::MissingContext)
}
}

Expand Down
44 changes: 34 additions & 10 deletions crates/openshell-crypto/src/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,21 @@ pub fn provider() -> CryptoProvider {
/// Returns the actual installed provider, including when another thread wins the
/// installation race. Callers must not infer backend identity from this operation.
pub fn ensure_default_provider() -> &'static Arc<CryptoProvider> {
if CryptoProvider::get_default().is_none() {
let _ = provider().install_default();
try_ensure_default_provider()
.expect("enable openshell-crypto/aws-lc or call install_default_context before TLS use")
}

/// Preserve the host provider or install the facade provider process-wide.
/// Returns an error if neither a host provider nor a backend is available.
pub fn try_ensure_default_provider() -> Result<&'static Arc<CryptoProvider>, crate::CryptoError> {
if let Some(provider) = CryptoProvider::get_default() {
return Ok(provider);
}
CryptoProvider::get_default().expect("a crypto provider was installed")
let _ = crate::try_default_context()?
.backend()
.tls_provider()
.install_default();
Ok(CryptoProvider::get_default().expect("a crypto provider was installed"))
}

/// Verification algorithms for custom TLS certificate verifiers.
Expand All @@ -34,7 +45,8 @@ pub fn signature_verification_algorithms() -> WebPkiSupportedAlgorithms {
pub fn any_supported_signing_key(
key: &rustls::pki_types::PrivateKeyDer<'_>,
) -> Result<Arc<dyn rustls::sign::SigningKey>, rustls::Error> {
configuration_provider()
try_configuration_provider()
.map_err(|e| rustls::Error::General(e.to_string()))?
.key_provider
.load_private_key(key.clone_key())
}
Expand All @@ -46,10 +58,18 @@ pub fn any_supported_signing_key(
/// embedder's installed provider, preserving the existing library contract.
#[must_use]
pub fn configuration_provider() -> Arc<CryptoProvider> {
try_configuration_provider()
.expect("enable openshell-crypto/aws-lc or call install_default_context before TLS use")
}

/// Fallible provider selection for runtime TLS configurations.
pub fn try_configuration_provider() -> Result<Arc<CryptoProvider>, crate::CryptoError> {
if crate::explicitly_selected() {
Arc::new(provider())
Ok(Arc::new(
crate::try_default_context()?.backend().tls_provider(),
))
} else {
ensure_default_provider().clone()
Ok(try_ensure_default_provider()?.clone())
}
}

Expand All @@ -66,8 +86,10 @@ pub fn try_client_builder()
pub fn try_client_builder_with_protocol_versions(
versions: &[&'static rustls::SupportedProtocolVersion],
) -> Result<rustls::ConfigBuilder<rustls::ClientConfig, rustls::WantsVerifier>, rustls::Error> {
rustls::ClientConfig::builder_with_provider(configuration_provider())
.with_protocol_versions(versions)
rustls::ClientConfig::builder_with_provider(
try_configuration_provider().map_err(|e| rustls::Error::General(e.to_string()))?,
)
.with_protocol_versions(versions)
}

/// Fallible server builder using the selected configuration provider.
Expand All @@ -83,8 +105,10 @@ pub fn try_server_builder()
pub fn try_server_builder_with_protocol_versions(
versions: &[&'static rustls::SupportedProtocolVersion],
) -> Result<rustls::ConfigBuilder<rustls::ServerConfig, rustls::WantsVerifier>, rustls::Error> {
rustls::ServerConfig::builder_with_provider(configuration_provider())
.with_protocol_versions(versions)
rustls::ServerConfig::builder_with_provider(
try_configuration_provider().map_err(|e| rustls::Error::General(e.to_string()))?,
)
.with_protocol_versions(versions)
}

/// Compatibility client builder. Use [`try_client_builder`] in runtime code.
Expand Down
1 change: 1 addition & 0 deletions crates/openshell-driver-db-credstore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ toml = { workspace = true }
tonic = { workspace = true }

[dev-dependencies]
openshell-crypto = { workspace = true, features = ["aws-lc"] }
tempfile = "3"
tokio = { workspace = true }

Expand Down
2 changes: 2 additions & 0 deletions crates/openshell-driver-docker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ name = "openshell-driver-docker"
path = "src/main.rs"

[dependencies]
# Product backend selection also applies when building without default features.
openshell-crypto = { workspace = true, features = ["aws-lc"] }
openshell-core = { path = "../openshell-core", default-features = false, features = ["driver-extraction"] }
openshell-isolation-interface = { path = "../openshell-isolation-interface" }
openshell-sandbox-backend = { path = "../openshell-sandbox-backend" }
Expand Down
2 changes: 1 addition & 1 deletion crates/openshell-driver-kubernetes-secrets/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ name = "openshell-driver-kubernetes-secrets"
path = "src/main.rs"

[dependencies]
openshell-crypto = { workspace = true, features = ["tls-kube"] }
openshell-crypto = { workspace = true, features = ["aws-lc", "tls-kube"] }
openshell-core = { path = "../openshell-core", default-features = false }

clap = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion crates/openshell-driver-kubernetes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ name = "openshell-driver-kubernetes"
path = "src/main.rs"

[dependencies]
openshell-crypto = { workspace = true, features = ["tls-kube"] }
openshell-crypto = { workspace = true, features = ["aws-lc", "tls-kube"] }
openshell-core = { path = "../openshell-core", default-features = false, features = ["jwt"] }
openshell-isolation-interface = { path = "../openshell-isolation-interface" }
openshell-sandbox-backend = { path = "../openshell-sandbox-backend" }
Expand Down
2 changes: 1 addition & 1 deletion crates/openshell-driver-mxc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ openshell-supervisor-network = { path = "../openshell-supervisor-network" }
windows = { workspace = true }

[dev-dependencies]
openshell-crypto = { workspace = true }
openshell-crypto = { workspace = true, features = ["aws-lc"] }
tokio = { workspace = true }
# tempfile is not a workspace dependency; 3.27 is already resolved in Cargo.lock.
tempfile = "3"
Expand Down
Loading
Loading