mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-20 06:00:06 +08:00
* feat(proxy): bind static credentials to provider endpoints Signed-off-by: John Myers <johntmyers@users.noreply.github.com> * test(e2e): verify static credential endpoint isolation Signed-off-by: John Myers <johntmyers@users.noreply.github.com> * docs(provider): explain static credential endpoint binding Signed-off-by: John Myers <johntmyers@users.noreply.github.com> * fix(e2e): use valid endpoint isolation fixtures Signed-off-by: John Myers <johntmyers@users.noreply.github.com> * docs(provider): explain static credential endpoint binding Signed-off-by: John Myers <johntmyers@users.noreply.github.com> * fix(credentials): preserve binding identity across rotations Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(proxy): enforce bindings across request lifecycle Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(proxy): close credential relay gaps Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * docs(credentials): clarify binding failure behavior Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(credentials): hash selected provider profile scope Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(proxy): resolve credentials after request admission Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * docs(credentials): clarify binding failure diagnostics Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(proxy): align single-route credential denials Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(credentials): harden endpoint-bound rotation Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(credentials): enforce identity and authority binding Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(credentials): snapshot provider environment atomically Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(e2e): include authority port in query proxy requests Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(credentials): close credential revocation gaps Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * docs(proxy): explain authority mismatch diagnostics Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(credentials): enforce binding lifecycle invariants Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(provider): reject credential config collisions Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(network): capture credential scope atomically Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(network): distinguish origin and absolute targets Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(provider): isolate endpointless profile credentials Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(network): normalize IPv6 request authorities Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * docs(credentials): clarify endpointless profile isolation Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * feat(policy): bind endpointless provider credentials Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(credentials): use current GCP placeholder revision Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * docs(providers): explain policy credential bindings Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(credentials): cover endpointless fail-closed invariant Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(policy): expect ambiguity rejection at creation Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(server): authenticate rebased policy requests Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * refactor(proxy): share credential mismatch finding builder Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(credentials): cover malformed binding metadata Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(credentials): verify multi-key endpoint isolation Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(e2e): cover same-host credential path denial Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * docs(credentials): document serialized refresh contract Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * refactor(proxy): consolidate L7 log formatting Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * perf(credentials): precompile endpoint binding patterns Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * perf(credentials): share identity epoch revisions Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(proxy): require explicit request default ports Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(policy): validate SigV4 credential sources Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(credentials): preserve endpoint bindings for credential handles Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * feat(go-sdk): expose network credential bindings Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> --------- Signed-off-by: John Myers <johntmyers@users.noreply.github.com> Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
103 lines
3.3 KiB
Rust
103 lines
3.3 KiB
Rust
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
//! Canonical provider endpoint path matching shared by policy and runtime code.
|
|
|
|
/// A compiled provider endpoint path pattern.
|
|
///
|
|
/// Invalid glob syntax retains the existing fail-closed behavior: it matches
|
|
/// only when the request path is exactly equal to the configured pattern.
|
|
#[derive(Debug, Clone)]
|
|
pub struct EndpointPathPattern {
|
|
source: String,
|
|
kind: EndpointPathPatternKind,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
enum EndpointPathPatternKind {
|
|
Any,
|
|
Subtree(String),
|
|
Glob(glob::Pattern),
|
|
Invalid,
|
|
}
|
|
|
|
impl EndpointPathPattern {
|
|
#[must_use]
|
|
pub fn new(pattern: &str) -> Self {
|
|
let kind = if pattern.is_empty() || pattern == "**" || pattern == "/**" {
|
|
EndpointPathPatternKind::Any
|
|
} else if let Some(prefix) = pattern.strip_suffix("/**") {
|
|
EndpointPathPatternKind::Subtree(prefix.to_string())
|
|
} else {
|
|
glob::Pattern::new(pattern).map_or(
|
|
EndpointPathPatternKind::Invalid,
|
|
EndpointPathPatternKind::Glob,
|
|
)
|
|
};
|
|
Self {
|
|
source: pattern.to_string(),
|
|
kind,
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn matches(&self, path: &str) -> bool {
|
|
if self.source == path {
|
|
return true;
|
|
}
|
|
match &self.kind {
|
|
EndpointPathPatternKind::Any => true,
|
|
EndpointPathPatternKind::Subtree(prefix) => {
|
|
path == prefix
|
|
|| path
|
|
.strip_prefix(prefix)
|
|
.is_some_and(|suffix| suffix.starts_with('/'))
|
|
}
|
|
EndpointPathPatternKind::Glob(pattern) => pattern.matches(path),
|
|
EndpointPathPatternKind::Invalid => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Return whether `path` is selected by a provider endpoint path pattern.
|
|
///
|
|
/// Empty paths and `**` match every request path. A trailing `/**` matches the
|
|
/// named path itself and every descendant. Other patterns use glob semantics.
|
|
#[must_use]
|
|
pub fn matches(pattern: &str, path: &str) -> bool {
|
|
EndpointPathPattern::new(pattern).matches(path)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{EndpointPathPattern, matches};
|
|
|
|
#[test]
|
|
fn matches_canonical_endpoint_patterns() {
|
|
assert!(matches("", "/v1/messages"));
|
|
assert!(matches("/**", "/v1/messages"));
|
|
assert!(matches("/v1/**", "/v1"));
|
|
assert!(matches("/v1/**", "/v1/messages"));
|
|
assert!(matches("/v*/messages", "/v1/messages"));
|
|
assert!(matches("/v1/*", "/v1/chat/messages"));
|
|
assert!(!matches("/v1/**", "/v2/messages"));
|
|
assert!(!matches("/v1/*/messages", "/v1/chat/completions"));
|
|
}
|
|
|
|
#[test]
|
|
fn compiled_patterns_preserve_canonical_matching() {
|
|
let subtree = EndpointPathPattern::new("/v1/**");
|
|
assert!(subtree.matches("/v1"));
|
|
assert!(subtree.matches("/v1/chat/messages"));
|
|
assert!(!subtree.matches("/v2/messages"));
|
|
|
|
let glob = EndpointPathPattern::new("/v*/messages");
|
|
assert!(glob.matches("/v1/messages"));
|
|
assert!(!glob.matches("/v1/completions"));
|
|
|
|
let invalid = EndpointPathPattern::new("[");
|
|
assert!(invalid.matches("["));
|
|
assert!(!invalid.matches("/v1/messages"));
|
|
}
|
|
}
|