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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ jobs:

- name: Run tests
run: |
for target in fuzz_openstack_sdk_config fuzz_openstack_sdk_config_yaml fuzz_link_header fuzz_next_page_from_body fuzz_expand_link fuzz_discovery_endpoints fuzz_api_version fuzz_api_error_from_openstack fuzz_auth_error_response fuzz_build_request_url fuzz_state_cache; do
for target in fuzz_openstack_sdk_config fuzz_openstack_sdk_config_yaml fuzz_link_header fuzz_next_page_from_body fuzz_expand_link fuzz_discovery_endpoints fuzz_api_version fuzz_api_error_from_openstack fuzz_auth_error_response fuzz_build_request_url fuzz_state_cache fuzz_wasm_plugin_identity_http_request fuzz_wasm_plugin_sso_build_response fuzz_wasm_plugin_auth_result; do
cargo +nightly fuzz run "$target" --features=fuzzing -- -max_total_time=60
done

Expand Down
81 changes: 63 additions & 18 deletions Cargo.lock

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

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ default-members = [
"sdk/auth-receipt",
"sdk/auth-totp",
"sdk/auth-websso",
"sdk/websso-host",
"sdk/core",
"sdk/block-storage",
"sdk/container-infrastructure-management",
Expand Down Expand Up @@ -94,18 +95,22 @@ openstack-cli-placement = { path="cli/placement/", version = "^0.13" }
openstack-cli-plugin = { path="cli/plugin/", version = "0.13.7" }
openstack-sdk-auth-core = { version = "0.22.6", path = "sdk/auth-core" }
openstack-sdk-plugin-wasm = { version = "0.1.0", path = "sdk/plugin-wasm" }
openstack-sdk-websso-host = { version = "0.1.0", path = "sdk/websso-host" }
openstack_sdk_core = { version = "0.22.7", path = "sdk/core" }
openstack_sdk = { version = "0.22.7", path = "openstack_sdk" }
openstack_types = { version = "0.22.7", path = "openstack_types" }
openstack-types-core = { version = "0.22", path = "types/core" }
pem = { version = "^4.0" }
regex = { version = "^1.13" }
rcgen = { version = "^0.13" }
ring = { version = "^0.17" }
rsa = { version = "^0.9", features = ["getrandom", "pkcs5"] }
ssh-key = { version = "^0.6", features = ["rsa", "encryption"] }
reqwest = { version = "^0.13", default-features = false }
reserve-port = "^2.5"
schemars = { version = "^1.2" }
secrecy = { version = "^0.10", features = ["serde"] }
semver = { version = "^1.0" }
serde = { version="^1.0", features=["derive"] }
serde_json = "^1.0"
serde_bytes = "^0.11"
Expand All @@ -125,6 +130,7 @@ url = { version = "^2.5", features = ["serde"] }
webauthn-authenticator-rs = { version = "^0.5", features = ["ctap2", "mozilla", "ui-cli"]}
webauthn-rs-proto = { version = "^0.5" }
uuid = { version = "^1.24" }
x509-parser = { version = "^0.16" }
zeroize = { version = "^1.9" }

[profile.dev]
Expand Down
1 change: 1 addition & 0 deletions cli/plugin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ repository.workspace = true

[dependencies]
clap.workspace = true
dialoguer.workspace = true
eyre.workspace = true
openstack-cli-core.workspace = true
openstack-sdk-plugin-wasm.workspace = true
Expand Down
89 changes: 89 additions & 0 deletions cli/plugin/src/confirm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

//! Shared install/update confirmation UX, used by both `osc plugin install`
//! and `osc plugin update`.

use std::io::IsTerminal;

use dialoguer::Confirm;

use openstack_cli_core::error::OpenStackCliError;
use openstack_sdk_plugin_wasm::registry::{PendingInstall, ProvenanceOutcome};

/// The single permission a WASM auth plugin's guest ABI can use today: an
/// HTTP request scoped to the identity provider origin passed at auth time.
/// See `sdk/plugin-wasm/src/plugin.rs` module docs — the guest ABI exposes
/// exactly one host function, so this is not currently derived per-plugin.
const PERMISSIONS_SUMMARY: &str =
"identity_http (HTTP requests to the identity provider origin passed at auth time)";

/// Print what's about to be installed/updated — publisher, source repo, the
/// permission this plugin ABI can use, checksum, and the provenance result
/// — then, unless `yes` is set, ask for interactive confirmation.
///
/// Returns `Ok(true)` to proceed, `Ok(false)` if the user declined. Errors
/// out (rather than prompting) in a non-interactive context without `--yes`,
/// so this never silently hangs on stdin.
pub fn confirm_pending(pending: &PendingInstall, yes: bool) -> Result<bool, OpenStackCliError> {
println!("Plugin: {}@{}", pending.name, pending.version);
println!("Source repo: {}", pending.source_repo);
println!("Permissions: {PERMISSIONS_SUMMARY}");
println!("SHA-256: {}", pending.sha256);
match &pending.provenance {
ProvenanceOutcome::Verified(record) => {
println!(
"Provenance: verified — published by CI in {} (OIDC issuer: {})",
record.source_repo,
record.oidc_issuer.as_deref().unwrap_or("unknown")
);
if let Some(workflow_ref) = &record.workflow_ref {
println!("Workflow: {workflow_ref}");
}
}
ProvenanceOutcome::Unverified { reason } => {
println!("Provenance: UNVERIFIED — {reason}");
}
}

if yes {
return Ok(true);
}
if !std::io::stdin().is_terminal() {
return Err(eyre::eyre!(
"refusing to install {}@{} without confirmation in a non-interactive context; pass --yes to proceed",
pending.name,
pending.version
)
.into());
}
Ok(Confirm::new()
.with_prompt(format!("Install {}@{}?", pending.name, pending.version))
.interact()?)
}

/// Unconditionally warn (stderr, not gated by log level, plus a structured
/// `tracing::warn!`) that a plugin is being trusted without provenance
/// verification. Call whenever `--allow-unsigned` is what made an
/// install/update proceed.
pub fn warn_allow_unsigned(name: &str, version: &str) {
eprintln!(
"WARNING: installing {name}@{version} without provenance verification (--allow-unsigned). This plugin's origin has not been cryptographically verified."
);
tracing::warn!(
name,
version,
"installing plugin without provenance verification (allow_unsigned)"
);
}
Loading
Loading