build(collab): enable local ABI v2 auth debugging
This commit is contained in:
parent
d17c26ab39
commit
da2c90fe9e
|
|
@ -16,6 +16,10 @@ default = []
|
|||
# Deterministic, non-production signing keys for downstream integration tests.
|
||||
# Production verifier configuration never trusts this fixture issuer.
|
||||
test-issuer = []
|
||||
# Explicit opt-in for an unsigned, external op-auth archive in local debug
|
||||
# builds. `build.rs` still requires the absolute archive path and ABI version,
|
||||
# and rejects this feature outside Cargo's debug profile.
|
||||
dev-abi-v2 = []
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.22"
|
||||
|
|
|
|||
|
|
@ -35,6 +35,29 @@ decryptor and key with the application adds obscurity, not a security boundary.
|
|||
Production trust never depends on client artifact secrecy: signing keys and
|
||||
ticket issuance remain server-side.
|
||||
|
||||
## Local ABI-v2 development
|
||||
|
||||
Developers can exercise the collaboration UI against a private ABI-v2 archive
|
||||
without replacing the committed ABI-v1 compatibility artifact:
|
||||
|
||||
```sh
|
||||
OPENPENCIL_DEV_OP_AUTH_ARCHIVE=/absolute/path/to/libop_auth.a \
|
||||
OPENPENCIL_DEV_OP_AUTH_ABI_VERSION=2 \
|
||||
cargo build -p op-host-desktop --features dev-op-auth-abi-v2
|
||||
```
|
||||
|
||||
Using the override requires the feature and both variables together; enabling
|
||||
the feature without either variable is a no-op so workspace `--all-features`
|
||||
checks keep using the committed artifact. The archive path must be absolute,
|
||||
must select a regular non-symlink file using the artifact name expected by the
|
||||
current target, and is watched for changes by Cargo. The build script copies it
|
||||
into Cargo's private build-output directory before linking. This override is
|
||||
accepted only in Cargo's debug profile when target debug assertions are
|
||||
enabled; release, release-derived, and hardened profiles reject it. It
|
||||
deliberately skips release provenance only for a local, non-shipping binary.
|
||||
The runtime ABI handshake and required collaboration symbols still fail
|
||||
closed.
|
||||
|
||||
## Regional login and collaboration trust
|
||||
|
||||
The credential-bearing login/ticket origin and the public collaboration trust
|
||||
|
|
|
|||
|
|
@ -3,19 +3,27 @@
|
|||
//! UI stays hidden. Open-source checkouts therefore always build.
|
||||
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[path = "prebuilt_provenance.rs"]
|
||||
mod prebuilt_provenance;
|
||||
|
||||
const DEV_ARCHIVE_ENV: &str = "OPENPENCIL_DEV_OP_AUTH_ARCHIVE";
|
||||
const DEV_ABI_VERSION_ENV: &str = "OPENPENCIL_DEV_OP_AUTH_ABI_VERSION";
|
||||
const DEV_FEATURE_ENV: &str = "CARGO_FEATURE_DEV_ABI_V2";
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rustc-check-cfg=cfg(op_auth_prebuilt)");
|
||||
println!("cargo:rustc-check-cfg=cfg(op_auth_collab_ticket_prebuilt)");
|
||||
println!("cargo:rustc-check-cfg=cfg(op_auth_development_prebuilt)");
|
||||
println!("cargo:rerun-if-changed=prebuilt");
|
||||
println!("cargo:rerun-if-env-changed={DEV_ARCHIVE_ENV}");
|
||||
println!("cargo:rerun-if-env-changed={DEV_ABI_VERSION_ENV}");
|
||||
|
||||
let target = env::var("TARGET").unwrap_or_default();
|
||||
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
let prebuilt_dir = manifest_dir.join("prebuilt").join(&target);
|
||||
// MSVC static libraries follow the `<name>.lib` convention; every
|
||||
// other target uses the Unix `lib<name>.a` archive name.
|
||||
let artifact = if target.ends_with("-pc-windows-msvc") {
|
||||
|
|
@ -23,28 +31,49 @@ fn main() {
|
|||
} else {
|
||||
"libop_auth.a"
|
||||
};
|
||||
let artifact_path = prebuilt_dir.join(artifact);
|
||||
if !artifact_path.is_file() {
|
||||
return;
|
||||
}
|
||||
let validated = match prebuilt_provenance::validate_prebuilt(
|
||||
&manifest_dir.join("prebuilt"),
|
||||
&prebuilt_dir,
|
||||
&target,
|
||||
artifact,
|
||||
&env::var("CARGO_PKG_VERSION").unwrap_or_default(),
|
||||
) {
|
||||
Ok(validated) => validated,
|
||||
Err(error) => {
|
||||
println!("cargo:warning=ignoring op-auth prebuilt: {error}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let abi_version = validated.abi_version;
|
||||
|
||||
let development = development_prebuilt(artifact);
|
||||
let (prebuilt_dir, abi_version, development_override, signed_provenance) =
|
||||
if let Some((directory, abi_version)) = development {
|
||||
println!(
|
||||
"cargo:warning=using unsigned local op-auth ABI {abi_version} \
|
||||
archive for a debug build"
|
||||
);
|
||||
println!("cargo:rustc-cfg=op_auth_development_prebuilt");
|
||||
(directory, abi_version, true, false)
|
||||
} else {
|
||||
let prebuilt_dir = manifest_dir.join("prebuilt").join(&target);
|
||||
let artifact_path = prebuilt_dir.join(artifact);
|
||||
if !artifact_path.is_file() {
|
||||
return;
|
||||
}
|
||||
let validated = match prebuilt_provenance::validate_prebuilt(
|
||||
&manifest_dir.join("prebuilt"),
|
||||
&prebuilt_dir,
|
||||
&target,
|
||||
artifact,
|
||||
&env::var("CARGO_PKG_VERSION").unwrap_or_default(),
|
||||
) {
|
||||
Ok(validated) => validated,
|
||||
Err(error) => {
|
||||
println!("cargo:warning=ignoring op-auth prebuilt: {error}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
(
|
||||
prebuilt_dir,
|
||||
validated.abi_version,
|
||||
false,
|
||||
validated.signed_provenance,
|
||||
)
|
||||
};
|
||||
|
||||
println!("cargo:rustc-cfg=op_auth_prebuilt");
|
||||
if abi_version == 2 {
|
||||
debug_assert!(validated.signed_provenance);
|
||||
assert!(
|
||||
development_override || signed_provenance,
|
||||
"production ABI-v2 archives require signed provenance"
|
||||
);
|
||||
println!("cargo:rustc-cfg=op_auth_collab_ticket_prebuilt");
|
||||
}
|
||||
println!("cargo:rustc-env=OP_AUTH_PREBUILT_ABI_VERSION={abi_version}");
|
||||
|
|
@ -66,3 +95,65 @@ fn main() {
|
|||
println!("cargo:rustc-link-lib=ntdll");
|
||||
}
|
||||
}
|
||||
|
||||
fn development_prebuilt(artifact: &str) -> Option<(PathBuf, u32)> {
|
||||
let feature_enabled = env::var_os(DEV_FEATURE_ENV).is_some();
|
||||
let archive = env::var_os(DEV_ARCHIVE_ENV);
|
||||
let abi_version = env::var_os(DEV_ABI_VERSION_ENV);
|
||||
let (archive, abi_version) = match (feature_enabled, archive, abi_version) {
|
||||
(_, None, None) => return None,
|
||||
(true, Some(archive), Some(abi_version)) => (archive, abi_version),
|
||||
(false, _, _) => {
|
||||
panic!("{DEV_ARCHIVE_ENV} requires the op-auth-bridge/dev-abi-v2 feature");
|
||||
}
|
||||
_ => {
|
||||
panic!(
|
||||
"op-auth-bridge/dev-abi-v2 requires {DEV_ARCHIVE_ENV} and {DEV_ABI_VERSION_ENV}"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let debug_build = env::var("PROFILE").is_ok_and(|profile| profile == "debug");
|
||||
assert!(
|
||||
debug_build,
|
||||
"{DEV_ARCHIVE_ENV} is accepted only in Cargo's debug profile"
|
||||
);
|
||||
|
||||
let requested_archive = PathBuf::from(archive);
|
||||
assert!(
|
||||
requested_archive.is_absolute(),
|
||||
"{DEV_ARCHIVE_ENV} must be an absolute path"
|
||||
);
|
||||
let metadata = fs::symlink_metadata(&requested_archive)
|
||||
.unwrap_or_else(|_| panic!("{DEV_ARCHIVE_ENV} is missing or unreadable"));
|
||||
assert!(
|
||||
metadata.file_type().is_file(),
|
||||
"{DEV_ARCHIVE_ENV} must select a regular non-symlink file"
|
||||
);
|
||||
let archive = fs::canonicalize(&requested_archive)
|
||||
.unwrap_or_else(|_| panic!("{DEV_ARCHIVE_ENV} is missing or unreadable"));
|
||||
assert!(
|
||||
archive.is_file(),
|
||||
"{DEV_ARCHIVE_ENV} must select a regular file"
|
||||
);
|
||||
assert_eq!(
|
||||
archive.file_name(),
|
||||
Some(OsStr::new(artifact)),
|
||||
"{DEV_ARCHIVE_ENV} must select the target's {artifact}"
|
||||
);
|
||||
|
||||
let abi_version = abi_version
|
||||
.into_string()
|
||||
.ok()
|
||||
.filter(|value| value == "2")
|
||||
.map(|_| 2)
|
||||
.unwrap_or_else(|| panic!("{DEV_ABI_VERSION_ENV} must be exactly 2"));
|
||||
let directory = PathBuf::from(
|
||||
env::var("OUT_DIR").expect("Cargo provides OUT_DIR to the op-auth build script"),
|
||||
);
|
||||
fs::copy(&archive, directory.join(artifact))
|
||||
.unwrap_or_else(|_| panic!("failed to stage {DEV_ARCHIVE_ENV} in OUT_DIR"));
|
||||
println!("cargo:rerun-if-changed={}", requested_archive.display());
|
||||
println!("cargo:rerun-if-changed={}", archive.display());
|
||||
Some((directory, abi_version))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@
|
|||
//! validation, and bounded JWKS caching are entirely open source. The private
|
||||
//! library may only issue opaque tickets from its authenticated device session.
|
||||
|
||||
#[cfg(all(op_auth_development_prebuilt, not(debug_assertions)))]
|
||||
compile_error!("the local op-auth archive override requires target debug assertions");
|
||||
|
||||
mod collab_claims;
|
||||
mod collab_jwks;
|
||||
mod collab_jwks_cache;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ path = "src/main.rs"
|
|||
[features]
|
||||
default = []
|
||||
mcp-debug-tools = ["op-mcp/debug-tools"]
|
||||
# Local-only collaboration development against an external private ABI-v2
|
||||
# archive. Release builds are rejected by op-auth-bridge's build script.
|
||||
dev-op-auth-abi-v2 = ["op-auth-bridge/dev-abi-v2"]
|
||||
|
||||
# File-association metadata for `cargo-bundle` (`cargo bundle`). This
|
||||
# declares OpenPencil as the OS-level handler for `.op` / `.pen`
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ collab_scan_roots=(
|
|||
crates/op-editor-ui/src
|
||||
crates/op-host-native/src
|
||||
crates/op-host-desktop/src
|
||||
crates/op-host-services/src/profile_avatar_fetch.rs
|
||||
crates/op-host-services/src/public_https_client.rs
|
||||
crates/op-host-services/src/provider_dial.rs
|
||||
crates/op-host-services/src/web_credentials.rs
|
||||
|
|
@ -512,9 +513,16 @@ for avatar_anchor in \
|
|||
"MAX_AVATAR_ENCODED_BYTES" \
|
||||
"public_https_client" \
|
||||
"REQUEST_TIMEOUT"; do
|
||||
require_literal crates/op-host-desktop/src/collab_avatar_host.rs \
|
||||
require_literal crates/op-host-services/src/profile_avatar_fetch.rs \
|
||||
"$avatar_anchor" "bounded collaboration avatar fetch"
|
||||
done
|
||||
for desktop_avatar_anchor in \
|
||||
"request.is_current_account()" \
|
||||
"fetch_account_avatar_blocking(request.url())" \
|
||||
"fetch_profile_avatar_blocking(request.url())"; do
|
||||
require_literal crates/op-host-desktop/src/collab_avatar_host.rs \
|
||||
"$desktop_avatar_anchor" "desktop avatar security-policy delegation"
|
||||
done
|
||||
require_literal crates/op-host-services/src/provider_dial.rs \
|
||||
".no_proxy()" "public HTTPS proxy bypass prevention"
|
||||
require_literal crates/op-host-services/src/provider_dial.rs \
|
||||
|
|
|
|||
|
|
@ -332,11 +332,21 @@ EOF
|
|||
fn public_fixture_is_explicitly_enabled() {}
|
||||
EOF
|
||||
|
||||
cat > "$fixture_root/crates/op-host-desktop/src/collab_avatar_host.rs" <<'EOF'
|
||||
cat > "$fixture_root/crates/op-host-services/src/profile_avatar_fetch.rs" <<'EOF'
|
||||
const MAX_REDIRECTS: usize = 3;
|
||||
const REQUEST_TIMEOUT: u64 = 5;
|
||||
const MAX_AVATAR_ENCODED_BYTES: usize = 1024;
|
||||
fn public_https_client() {}
|
||||
EOF
|
||||
|
||||
cat > "$fixture_root/crates/op-host-desktop/src/collab_avatar_host.rs" <<'EOF'
|
||||
fn dispatch(request: AvatarRequest) {
|
||||
if request.is_current_account() {
|
||||
fetch_account_avatar_blocking(request.url());
|
||||
} else {
|
||||
fetch_profile_avatar_blocking(request.url());
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
cat > "$fixture_root/crates/op-host-services/src/public_https_client.rs" <<'EOF'
|
||||
|
|
@ -634,6 +644,26 @@ new_fixture desktop-sensitive-file
|
|||
expect_failure "rejects sensitive files in desktop collaboration integration" \
|
||||
"sensitive key/token-shaped files are forbidden"
|
||||
|
||||
new_fixture avatar-redirect-limit-removed
|
||||
sed '/MAX_REDIRECTS/d' \
|
||||
"$fixture_root/crates/op-host-services/src/profile_avatar_fetch.rs" \
|
||||
> "$fixture_root/crates/op-host-services/src/profile_avatar_fetch.rs.next"
|
||||
mv \
|
||||
"$fixture_root/crates/op-host-services/src/profile_avatar_fetch.rs.next" \
|
||||
"$fixture_root/crates/op-host-services/src/profile_avatar_fetch.rs"
|
||||
expect_failure "requires the shared avatar redirect limit" \
|
||||
"bounded collaboration avatar fetch"
|
||||
|
||||
new_fixture desktop-public-avatar-delegation-removed
|
||||
sed '/fetch_profile_avatar_blocking(request.url())/d' \
|
||||
"$fixture_root/crates/op-host-desktop/src/collab_avatar_host.rs" \
|
||||
> "$fixture_root/crates/op-host-desktop/src/collab_avatar_host.rs.next"
|
||||
mv \
|
||||
"$fixture_root/crates/op-host-desktop/src/collab_avatar_host.rs.next" \
|
||||
"$fixture_root/crates/op-host-desktop/src/collab_avatar_host.rs"
|
||||
expect_failure "requires public-only desktop collaboration avatar delegation" \
|
||||
"desktop avatar security-policy delegation"
|
||||
|
||||
new_fixture avatar-proxy-bypass-removed
|
||||
: > "$fixture_root/crates/op-host-services/src/provider_dial.rs"
|
||||
expect_failure "requires proxy-free pinned avatar dialing" \
|
||||
|
|
|
|||
Loading…
Reference in a new issue