Files
OpenShell/crates/openshell-core/build.rs
Taylor Mutch 702cbc4f63 feat(providers): support SPIFFE-backed token grants (#1784)
* feat(providers): support SPIFFE-backed token grants

Add provider profile token_grant metadata and expand endpoint-specific
dynamic credentials so sandbox supervisors can request SPIFFE JWT-SVIDs,
exchange them with an OAuth-style token endpoint, cache returned access
tokens, and inject bearer tokens into matching HTTP requests.

Wire Kubernetes and Helm deployments to mount the provider SPIFFE Workload
API socket into sandbox pods for token grant exchange.

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
Signed-off-by: Gordon Sim <gsim@redhat.com>

* test(examples): add SPIFFE token grant demo

Add a reusable alpha/beta demo that deploys a SPIFFE-verifying token issuer
and protected services, imports a token-grant provider profile, creates a
sandbox, and verifies endpoint-specific bearer tokens.

The script leaves Kubernetes workloads in place, deletes sandboxes through
openshell unless KEEP_SANDBOX=1, and prints protected service logs as proof
of life.

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(providers): harden SPIFFE token grants

* fix(providers): harden dynamic token grants

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(providers): harden token grant handling

---------

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
Signed-off-by: Gordon Sim <gsim@redhat.com>
2026-06-10 10:54:39 -07:00

125 lines
4.6 KiB
Rust

// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
use std::env;
use std::path::{Path, PathBuf};
const PROTO_REL: &str = "../../proto";
fn main() -> Result<(), Box<dyn std::error::Error>> {
// --- Git-derived version ---
// Compute a version from `git describe` for local builds. In Docker/CI
// builds where .git is absent, this silently does nothing and the binary
// falls back to CARGO_PKG_VERSION (which is already sed-patched by the
// build pipeline).
println!("cargo:rerun-if-changed=../../.git/HEAD");
println!("cargo:rerun-if-changed=../../.git/refs/tags");
if let Some(version) = git_version() {
println!("cargo:rustc-env=OPENSHELL_GIT_VERSION={version}");
}
// --- Protobuf compilation ---
// Re-run when anything under proto/ changes (including newly added .proto files).
println!("cargo:rerun-if-changed={PROTO_REL}");
// Use bundled protoc from protobuf-src. The system protoc (from apt-get)
// does not bundle the well-known type includes (google/protobuf/struct.proto
// etc.), so we must use protobuf-src which ships both the binary and the
// include tree.
// SAFETY: This is run at build time in a single-threaded build script context.
// No other threads are reading environment variables concurrently.
#[allow(unsafe_code)]
unsafe {
env::set_var("PROTOC", protobuf_src::protoc());
}
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
let proto_root = manifest_dir.join(PROTO_REL);
let mut proto_files = Vec::new();
collect_proto_files(&proto_root, &mut proto_files)?;
proto_files.sort();
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
let descriptor_path = out_dir.join("openshell_descriptor.bin");
// Configure tonic/prost protobuf code generation.
tonic_prost_build::configure()
.build_server(true)
.build_client(true)
// Emit a binary FileDescriptorSet so the server can enumerate every
// RPC at runtime (used by the per-handler auth exhaustiveness test).
.file_descriptor_set_path(&descriptor_path)
.compile_protos(&proto_files, &[proto_root])?;
println!(
"cargo:rustc-env=OPENSHELL_DESCRIPTOR_PATH={}",
descriptor_path.display()
);
Ok(())
}
fn collect_proto_files(dir: &Path, out: &mut Vec<PathBuf>) -> std::io::Result<()> {
for entry in std::fs::read_dir(dir)? {
let path = entry?.path();
if path.is_dir() {
collect_proto_files(&path, out)?;
} else if path.extension().is_some_and(|ext| ext == "proto") {
out.push(path);
}
}
Ok(())
}
/// Derive a version string from `git describe --tags`.
///
/// Implements the "guess-next-dev" convention used by the release pipeline
/// (`setuptools-scm`): when there are commits past the last tag, the patch
/// version is bumped and `-dev.<N>+g<sha>` is appended.
///
/// Examples:
/// on tag v0.0.3 → "0.0.3"
/// 3 commits past v0.0.3 → "0.0.4-dev.3+g2bf9969"
///
/// Returns `None` when git is unavailable or the repo has no matching tags.
fn git_version() -> Option<String> {
// Match numeric release tags only (e.g. `v0.0.29`). The bare glob `v*`
// also matches non-release tags like `vm-dev` or `vm-prod`; when one of
// those lands on the same commit as a release tag, `git describe` picks
// it and the resulting version string collapses to `m-dev` after the
// leading `v` is stripped below. Requiring a digit after `v` excludes
// those development tags without losing any release tag.
let output = std::process::Command::new("git")
.args(["describe", "--tags", "--long", "--match", "v[0-9]*"])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let desc = String::from_utf8(output.stdout).ok()?;
let desc = desc.trim();
let desc = desc.strip_prefix('v').unwrap_or(desc);
// `git describe --long` format: <tag>-<N>-g<sha>
// Split from the right to handle tags that contain hyphens.
let (rest, sha) = desc.rsplit_once('-')?;
let (tag, commits_str) = rest.rsplit_once('-')?;
let commits: u32 = commits_str.parse().ok()?;
if commits == 0 {
// Exactly on a tag — use the tag version as-is.
return Some(tag.to_string());
}
// Bump patch version (guess-next-dev scheme).
let mut parts = tag.splitn(3, '.');
let major = parts.next()?;
let minor = parts.next()?;
let patch: u32 = parts.next()?.parse().ok()?;
Some(format!("{major}.{minor}.{}-dev.{commits}+{sha}", patch + 1))
}