Skip to content

Commit b04c4a4

Browse files
committed
fix(cli): add auth and TLS support to completion client
The completion gRPC client used a bare channel with only mTLS, causing tab-completion to silently fail for gateways using OIDC or Cloudflare JWT authentication, or gateways with non-public CAs. - Load OIDC/Cloudflare JWT tokens from gateway metadata and attach an EdgeAuthInterceptor (matching the main CLI client path). - Replace manual TLS setup with build_channel, which gracefully falls back to CA-only or system root TLS when client certs are unavailable. - Respect OPENSHELL_GATEWAY_INSECURE env var so completions work with gateways using non-public CAs (the completer cannot see CLI flags).
1 parent 77e6c7a commit b04c4a4

1 file changed

Lines changed: 47 additions & 14 deletions

File tree

crates/openshell-cli/src/completers.rs

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,20 @@
33

44
use std::ffi::OsStr;
55
use std::future::Future;
6-
use std::time::Duration;
76

87
use clap_complete::engine::CompletionCandidate;
8+
use openshell_bootstrap::edge_token::load_edge_token;
9+
use openshell_bootstrap::oidc_token::{is_token_expired, load_oidc_token, store_oidc_token};
910
use openshell_bootstrap::{list_gateways, load_active_gateway, load_gateway_metadata};
1011
use openshell_core::ObjectName;
12+
use openshell_core::auth::EdgeAuthInterceptor;
1113
use openshell_core::proto::open_shell_client::OpenShellClient;
1214
use openshell_core::proto::{ListProvidersRequest, ListSandboxesRequest};
13-
use tonic::transport::{Channel, Endpoint};
15+
use tonic::service::interceptor::InterceptedService;
16+
use tonic::transport::Channel;
1417

15-
use crate::tls::{TlsOptions, build_tonic_tls_config, require_tls_materials};
18+
use crate::oidc_auth::oidc_refresh_token;
19+
use crate::tls::{TlsOptions, build_channel};
1620

1721
/// Complete gateway names from local metadata files (no network call).
1822
pub fn complete_gateway_names(_prefix: &OsStr) -> Vec<CompletionCandidate> {
@@ -84,17 +88,46 @@ fn resolve_active_gateway() -> Option<(String, String)> {
8488
async fn completion_grpc_client(
8589
server: &str,
8690
gateway_name: &str,
87-
) -> Option<OpenShellClient<Channel>> {
88-
let tls_opts = TlsOptions::default().with_gateway_name(gateway_name);
89-
let materials = require_tls_materials(server, &tls_opts).ok()?;
90-
let tls_config = build_tonic_tls_config(&materials);
91-
let endpoint = Endpoint::from_shared(server.to_string())
92-
.ok()?
93-
.connect_timeout(Duration::from_secs(2))
94-
.tls_config(tls_config)
95-
.ok()?;
96-
let channel = endpoint.connect().await.ok()?;
97-
Some(OpenShellClient::new(channel))
91+
) -> Option<OpenShellClient<InterceptedService<Channel, EdgeAuthInterceptor>>> {
92+
let mut tls_opts = TlsOptions::default().with_gateway_name(gateway_name);
93+
tls_opts.gateway_insecure = std::env::var("OPENSHELL_GATEWAY_INSECURE")
94+
.is_ok_and(|v| !v.is_empty() && v != "0" && v != "false");
95+
96+
if let Ok(meta) = load_gateway_metadata(gateway_name) {
97+
match meta.auth_mode.as_deref() {
98+
Some("oidc") => {
99+
if let Some(bundle) = load_oidc_token(gateway_name) {
100+
if is_token_expired(&bundle) {
101+
match oidc_refresh_token(&bundle).await {
102+
Ok(refreshed) => {
103+
let _ = store_oidc_token(gateway_name, &refreshed);
104+
tls_opts.oidc_token = Some(refreshed.access_token);
105+
}
106+
Err(_) => {
107+
tls_opts.oidc_token = Some(bundle.access_token);
108+
}
109+
}
110+
} else {
111+
tls_opts.oidc_token = Some(bundle.access_token);
112+
}
113+
}
114+
}
115+
Some("cloudflare_jwt") => {
116+
if let Some(token) = load_edge_token(gateway_name) {
117+
tls_opts.edge_token = Some(token);
118+
}
119+
}
120+
_ => {}
121+
}
122+
}
123+
124+
let channel = build_channel(server, &tls_opts).await.ok()?;
125+
let interceptor = EdgeAuthInterceptor::new(
126+
tls_opts.oidc_token.as_deref(),
127+
tls_opts.edge_token.as_deref(),
128+
)
129+
.ok()?;
130+
Some(OpenShellClient::with_interceptor(channel, interceptor))
98131
}
99132

100133
/// Run an async future on a dedicated thread to avoid nested tokio runtime panics.

0 commit comments

Comments
 (0)