From 21fea95935035c09f76dc1f2b1f82dd5d4196fac Mon Sep 17 00:00:00 2001 From: Simon Scatton <44714756+SDAChess@users.noreply.github.com> Date: Wed, 30 Sep 2026 14:49:09 +0000 Subject: [PATCH 01/33] test(tmachine): add K3s conformance scenario (#3848) * test(tmachine): add K3s conformance scenario Signed-off-by: Simon Scatton * refactor(tmachine): use Helm values file for K3s installer Signed-off-by: Simon Scatton * ci(tmachine): run K3s conformance in integration jobs Signed-off-by: Simon Scatton * ci(tmachine): verify installer scripts and document version baseline Signed-off-by: Simon Scatton --------- Signed-off-by: Simon Scatton --- .github/workflows/branch-e2e.yml | 1 + .github/workflows/integration-runner.yml | 2 + .github/workflows/integration-test.yml | 1 + .../workflows/prepare-integration-inputs.yml | 9 +- CI.md | 7 + tests/ansible/playbooks/k3s.yaml | 67 +++++++ tests/ansible/playbooks/openshell-k3s.yaml | 175 ++++++++++++++++++ tests/config.nix | 24 +++ 8 files changed, 285 insertions(+), 1 deletion(-) create mode 100644 tests/ansible/playbooks/k3s.yaml create mode 100644 tests/ansible/playbooks/openshell-k3s.yaml diff --git a/.github/workflows/branch-e2e.yml b/.github/workflows/branch-e2e.yml index 4b939f79d2..49cf199a3c 100644 --- a/.github/workflows/branch-e2e.yml +++ b/.github/workflows/branch-e2e.yml @@ -224,6 +224,7 @@ jobs: test-matrix: >- [ {"environment":"ubuntu-docker-rootful","installer":"binaries","testsuite":"conformance"}, + {"environment":"ubuntu-k3s","installer":"k3s","testsuite":"conformance"}, {"environment":"fedora-podman-rootful","installer":"binaries","testsuite":"conformance"}, {"environment":"fedora-podman-rootless","installer":"binaries","testsuite":"conformance"} ] diff --git a/.github/workflows/integration-runner.yml b/.github/workflows/integration-runner.yml index 1048e5abd3..768879c7b8 100644 --- a/.github/workflows/integration-runner.yml +++ b/.github/workflows/integration-runner.yml @@ -25,6 +25,7 @@ on: default: >- [ {"environment":"ubuntu-docker-rootful","installer":"deb","testsuite":"conformance"}, + {"environment":"ubuntu-k3s","installer":"k3s","testsuite":"conformance"}, {"environment":"fedora-podman-rootful","installer":"binaries","testsuite":"conformance"}, {"environment":"fedora-podman-rootless","installer":"binaries","testsuite":"conformance"} ] @@ -67,6 +68,7 @@ jobs: - uses: ./.github/actions/setup-nix - name: Cache tmachine disks + if: matrix.environment != 'ubuntu-k3s' uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 with: path: ~/.cache/tmachine diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index bf23de2ae5..bc91802882 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -23,6 +23,7 @@ on: default: >- [ {"environment":"ubuntu-docker-rootful","installer":"binaries","testsuite":"conformance"}, + {"environment":"ubuntu-k3s","installer":"k3s","testsuite":"conformance"}, {"environment":"fedora-podman-rootful","installer":"binaries","testsuite":"conformance"}, {"environment":"fedora-podman-rootless","installer":"binaries","testsuite":"conformance"} ] diff --git a/.github/workflows/prepare-integration-inputs.yml b/.github/workflows/prepare-integration-inputs.yml index 0db9dbe61e..77be3181d0 100644 --- a/.github/workflows/prepare-integration-inputs.yml +++ b/.github/workflows/prepare-integration-inputs.yml @@ -89,12 +89,16 @@ jobs: - name: Log in to GHCR run: echo "${{ github.token }}" | docker login ghcr.io -u "${GITHUB_ACTOR}" --password-stdin - - name: Export runtime images + - name: Export OpenShell images env: IMAGE_TAG: ${{ steps.artifact-run.outputs.source_sha }} run: | mkdir -p artifacts/images + docker pull "ghcr.io/nvidia/openshell/gateway:${IMAGE_TAG}" + docker tag "ghcr.io/nvidia/openshell/gateway:${IMAGE_TAG}" openshell/gateway:tmachine + docker save --output artifacts/images/openshell-gateway-tmachine.tar openshell/gateway:tmachine + docker pull "ghcr.io/nvidia/openshell/sandbox:${IMAGE_TAG}" docker tag "ghcr.io/nvidia/openshell/sandbox:${IMAGE_TAG}" openshell/sandbox:tmachine docker save --output artifacts/images/openshell-sandbox-tmachine.tar openshell/sandbox:tmachine @@ -109,6 +113,9 @@ jobs: - name: Build test workload images run: nix run .#build-artifacts-test-images + - name: Package Helm chart + run: nix run .#build-artifacts-helm + - name: Upload integration inputs id: upload-integration-inputs uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/CI.md b/CI.md index fb305d1b92..24bee35696 100644 --- a/CI.md +++ b/CI.md @@ -39,6 +39,13 @@ The GitHub ruleset should require the `OpenShell / ...` statuses published by `Required CI Gates` plus the direct `OpenShell / Trivy Changes` result, not the push-triggered workflow jobs themselves. +### K3s conformance version baseline + +The tmachine `ubuntu-k3s` conformance lane pins Agent Sandbox v0.5.0 as the +compatibility baseline for the v1beta1 Sandbox API. It does not track the local +K3s development default, currently v1.0.3. OpenShell also supports v0.4.6 through +its v1alpha1 fallback, so v0.5.0 is not the overall minimum supported version. + ### Run only the policy advisor conformance tests Manually dispatch `Integration Tests` on the candidate branch with an diff --git a/tests/ansible/playbooks/k3s.yaml b/tests/ansible/playbooks/k3s.yaml new file mode 100644 index 0000000000..e1a2aa3397 --- /dev/null +++ b/tests/ansible/playbooks/k3s.yaml @@ -0,0 +1,67 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +--- +- name: Install K3s and Helm + hosts: all + become: true + gather_facts: false + vars: + helm_version: 4.2.0 + k3s_version: 1.36.3+k3s1 + tasks: + - name: Wait for SSH + ansible.builtin.wait_for_connection: + + - name: Install download prerequisites + ansible.builtin.apt: + name: + - ca-certificates + - curl + state: present + update_cache: true + + - name: Download K3s installer + ansible.builtin.get_url: + url: https://get.k3s.io + checksum: sha256:e5cc3b3d9dfc1662c2d9be6da5abc9a4cd317d6abc3a5ffc02e3dd3248207fee + dest: /tmp/install-k3s.sh + mode: "0755" + + - name: Install K3s + ansible.builtin.command: + argv: + - /tmp/install-k3s.sh + - server + - --disable=servicelb + - --disable=traefik + creates: /usr/local/bin/k3s + environment: + INSTALL_K3S_VERSION: "v{{ k3s_version }}" + + - name: Wait for K3s + ansible.builtin.command: + argv: + - /usr/local/bin/k3s + - kubectl + - wait + - --for=condition=Ready + - node + - --all + - --timeout=120s + changed_when: false + + - name: Download Helm installer + ansible.builtin.get_url: + url: https://raw.githubusercontent.com/helm/helm/06468084e85c244c712834933d25ea232a4c2093/scripts/get-helm-4 + checksum: sha256:b68c5f694cff19f14ee8a5784ffd3de27fa7034ec8f973d703fc6fb85496ced7 + dest: /tmp/install-helm.sh + mode: "0755" + + - name: Install Helm + ansible.builtin.command: + argv: + - /tmp/install-helm.sh + creates: /usr/local/bin/helm + environment: + DESIRED_VERSION: "v{{ helm_version }}" diff --git a/tests/ansible/playbooks/openshell-k3s.yaml b/tests/ansible/playbooks/openshell-k3s.yaml new file mode 100644 index 0000000000..dd00aa4bda --- /dev/null +++ b/tests/ansible/playbooks/openshell-k3s.yaml @@ -0,0 +1,175 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +--- +- name: Install OpenShell on K3s + hosts: all + become: true + gather_facts: false + tasks: + - name: Wait for SSH + ansible.builtin.wait_for_connection: + + - name: Install OpenShell CLI + ansible.builtin.copy: + src: "{{ openshell_cli_binary }}" + dest: /usr/local/bin/openshell + mode: "0755" + + - name: Create OpenShell artifact directory + ansible.builtin.file: + path: /var/lib/openshell/artifacts + state: directory + mode: "0700" + + - name: Copy OpenShell images + ansible.builtin.copy: + src: "{{ item.src }}" + dest: "/var/lib/openshell/artifacts/{{ item.name }}.tar" + mode: "0600" + loop: + - name: gateway + src: "{{ openshell_gateway_image }}" + - name: sandbox + src: "{{ openshell_sandbox_image }}" + - name: supervisor + src: "{{ openshell_supervisor_image }}" + + - name: Import OpenShell images into K3s + ansible.builtin.command: + argv: + - /usr/local/bin/k3s + - ctr + - --namespace + - k8s.io + - images + - import + - "/var/lib/openshell/artifacts/{{ item }}.tar" + loop: + - gateway + - sandbox + - supervisor + + - name: Copy OpenShell Helm chart + ansible.builtin.copy: + src: "{{ openshell_helm_chart }}" + dest: /var/lib/openshell/artifacts/helm-chart.tgz + mode: "0600" + + - name: Write OpenShell Helm values + ansible.builtin.copy: + dest: /var/lib/openshell/artifacts/values.yaml + mode: "0600" + content: | + global: + image: + registry: "" + gateway: + image: + repository: openshell/gateway + tag: tmachine + pullPolicy: Never + sandboxRuntime: + image: + repository: openshell/sandbox + tag: tmachine + pullPolicy: never + supervisor: + image: + repository: openshell/supervisor + tag: tmachine + pullPolicy: never + networkPolicy: + enabled: true + server: + auth: + allowUnauthenticatedUsers: true + disableTls: true + telemetryEnabled: false + + - name: Install Agent Sandbox + ansible.builtin.command: + argv: + - /usr/local/bin/k3s + - kubectl + - apply + - --filename + - "https://github.com/kubernetes-sigs/agent-sandbox/releases/download/v{{ agent_sandbox_version }}/manifest.yaml" + + - name: Wait for Agent Sandbox CRD + ansible.builtin.command: + argv: + - /usr/local/bin/k3s + - kubectl + - wait + - --for=condition=Established + - crd/sandboxes.agents.x-k8s.io + - --timeout=120s + changed_when: false + + - name: Wait for Agent Sandbox controller + ansible.builtin.command: + argv: + - /usr/local/bin/k3s + - kubectl + - --namespace + - agent-sandbox-system + - rollout + - status + - deployment/agent-sandbox-controller + - --timeout=300s + changed_when: false + + - name: Install OpenShell Helm chart + ansible.builtin.command: + argv: + - /usr/local/bin/helm + - install + - openshell + - /var/lib/openshell/artifacts/helm-chart.tgz + - --namespace + - openshell + - --create-namespace + - --values + - /var/lib/openshell/artifacts/values.yaml + - --wait + - --timeout=5m + environment: + KUBECONFIG: /etc/rancher/k3s/k3s.yaml + + - name: Install gateway port-forward service + ansible.builtin.copy: + dest: /etc/systemd/system/openshell-k3s-port-forward.service + mode: "0644" + content: | + [Unit] + Description=OpenShell K3s gateway port forward + After=k3s.service + Requires=k3s.service + + [Service] + ExecStart=/usr/local/bin/k3s kubectl --namespace openshell port-forward --address 127.0.0.1 service/openshell 17670:8080 + Restart=always + RestartSec=1 + + [Install] + WantedBy=multi-user.target + + - name: Start gateway port-forward service + ansible.builtin.systemd_service: + name: openshell-k3s-port-forward.service + daemon_reload: true + enabled: true + state: started + + - name: Wait for OpenShell gateway + ansible.builtin.wait_for: + host: 127.0.0.1 + port: 17670 + timeout: 60 + +- name: Register OpenShell gateway for test client + hosts: all + gather_facts: false + roles: + - openshell_client diff --git a/tests/config.nix b/tests/config.nix index a19b81cee8..4bf7488ff8 100644 --- a/tests/config.nix +++ b/tests/config.nix @@ -43,6 +43,17 @@ let ]; }; } + { + name = "ubuntu-k3s"; + machine = "ubuntu"; + setup = { + use_galaxy = false; + playbooks = [ + "ansible/playbooks/nextest.yaml" + "ansible/playbooks/k3s.yaml" + ]; + }; + } { name = "fedora-podman-rootful"; machine = "fedora"; @@ -70,6 +81,19 @@ let ]; installers = [ + { + name = "k3s"; + use_galaxy = false; + playbooks = [ "ansible/playbooks/openshell-k3s.yaml" ]; + inputs = { + agent_sandbox_version = "0.5.0"; + openshell_cli_binary = "../artifacts/binaries/${muslTarget}/openshell"; + openshell_gateway_image = "../artifacts/images/openshell-gateway-tmachine.tar"; + openshell_helm_chart = "../artifacts/helm/helm-chart-0.0.0.tgz"; + openshell_sandbox_image = "../artifacts/images/openshell-sandbox-tmachine.tar"; + openshell_supervisor_image = "../artifacts/images/openshell-supervisor-tmachine.tar"; + }; + } { name = "none"; use_galaxy = false; From 7caff12d3c9013e7063d22391a76f775f6ce93b5 Mon Sep 17 00:00:00 2001 From: krishicks Date: Wed, 30 Sep 2026 15:19:39 +0000 Subject: [PATCH 02/33] perf(otel): stop exporting spans from steady-state polling (#3915) Store operation spans and request spans for supervisor-polled RPCs (GetSandboxConfig, ReportProviderReadiness) use DEBUG level, so the default INFO filter no longer exports them. The provider credential refresh worker opens its span only when a state has work. Refs #2698 Signed-off-by: Kris Hicks --- crates/openshell-server/src/multiplex.rs | 40 ++++++- .../openshell-server/src/persistence/mod.rs | 22 ++++ .../openshell-server/src/provider_refresh.rs | 108 +++++++++++------- docs/how-it-works/gateways/configuration.mdx | 2 +- 4 files changed, 130 insertions(+), 42 deletions(-) diff --git a/crates/openshell-server/src/multiplex.rs b/crates/openshell-server/src/multiplex.rs index 8988542b1a..a909ff2bb7 100644 --- a/crates/openshell-server/src/multiplex.rs +++ b/crates/openshell-server/src/multiplex.rs @@ -63,6 +63,16 @@ impl MakeRequestId for UuidRequestId { } } +/// Paths called on a timer rather than by someone waiting on the result. +const POLLED_PATHS: &[&str] = &[ + "/health", + "/healthz", + "/readyz", + "/openshell.v1.OpenShell/GetSandboxConfig", + "/openshell.v1.OpenShell/ReportProviderReadiness", + "/openshell.v1.OpenShell/PeerReportProviderReadiness", +]; + /// Build a tracing span for an inbound request, recording the `request_id` /// header (set by [`UuidRequestId`] or supplied by the client). fn make_request_span(req: &Request) -> Span { @@ -79,7 +89,7 @@ fn make_request_span(req: &Request) -> Span { // the callsite name. let otel_name = otel_span_name(req.method(), path); - let span = if matches!(path, "/health" | "/healthz" | "/readyz") { + let span = if POLLED_PATHS.contains(&path) { tracing::debug_span!( "request", method = %req.method(), @@ -2223,6 +2233,34 @@ mod tests { ); } + #[test] + fn polled_paths_get_debug_request_spans() { + let _traced = crate::otel_tracing::test_exporter::install_traced(); + let level = |path: &str| { + let req = Request::builder() + .uri(path) + .body(Empty::::new()) + .unwrap(); + *make_request_span(&req) + .metadata() + .expect("span enabled") + .level() + }; + + for path in [ + "/healthz", + "/openshell.v1.OpenShell/GetSandboxConfig", + "/openshell.v1.OpenShell/ReportProviderReadiness", + "/openshell.v1.OpenShell/PeerReportProviderReadiness", + ] { + assert_eq!(level(path), tracing::Level::DEBUG, "{path}"); + } + assert_eq!( + level("/openshell.v1.OpenShell/CreateSandbox"), + tracing::Level::INFO + ); + } + /// The `TraceLayer` creates the server span, so no gRPC handler needs /// `#[instrument]`. The request ID carries into it so a trace can be /// correlated with the gateway's logs. diff --git a/crates/openshell-server/src/persistence/mod.rs b/crates/openshell-server/src/persistence/mod.rs index c3a6649252..2513e80c10 100644 --- a/crates/openshell-server/src/persistence/mod.rs +++ b/crates/openshell-server/src/persistence/mod.rs @@ -360,6 +360,7 @@ impl Store { #[allow(clippy::too_many_arguments)] #[tracing::instrument( name = "store", + level = "debug", skip_all, fields(otel.name = "store.put_if", otel.status_code = tracing::field::Empty, object_type = %object_type, object.id = %id, object.name = %name, workspace = %workspace) )] @@ -394,6 +395,7 @@ impl Store { /// anything must use [`Self::put_if`], which is always durable. #[tracing::instrument( name = "store", + level = "debug", skip_all, fields(otel.name = "store.create_relaxed", otel.status_code = tracing::field::Empty, object_type = %object_type, object.id = %id, object.name = %name, workspace = %workspace) )] @@ -429,6 +431,7 @@ impl Store { /// * `Err(Conflict)` - Resource version mismatch #[tracing::instrument( name = "store", + level = "debug", skip_all, fields(otel.name = "store.delete_if", otel.status_code = tracing::field::Empty, object_type = %object_type, object.id = %id) )] @@ -445,6 +448,7 @@ impl Store { #[allow(clippy::too_many_arguments)] #[tracing::instrument( name = "store", + level = "debug", skip_all, fields(otel.name = "store.put_scoped", otel.status_code = tracing::field::Empty, object_type = %object_type, object.id = %id, object.name = %name, workspace = %workspace, scope = %scope) )] @@ -477,6 +481,7 @@ impl Store { #[allow(clippy::too_many_arguments)] #[tracing::instrument( name = "store", + level = "debug", skip_all, fields(otel.name = "store.create_scoped", otel.status_code = tracing::field::Empty, object_type = %object_type, object.id = %id, object.name = %name, workspace = %workspace, scope = %scope) )] @@ -510,6 +515,7 @@ impl Store { #[allow(clippy::too_many_arguments)] #[tracing::instrument( name = "store", + level = "debug", skip_all, fields(otel.name = "store.create_if_workspace_count_below", otel.status_code = tracing::field::Empty, object_type = %object_type, object.id = %id, object.name = %name, workspace = %workspace, max_count = max_count) )] @@ -537,6 +543,7 @@ impl Store { /// Fetch an object by id. #[tracing::instrument( name = "store", + level = "debug", skip_all, fields(otel.name = "store.get", otel.status_code = tracing::field::Empty, object_type = %object_type, object.id = %id) )] @@ -551,6 +558,7 @@ impl Store { /// Fetch an object by name within an object type and workspace. #[tracing::instrument( name = "store", + level = "debug", skip_all, fields( otel.name = "store.get_by_name", otel.status_code = tracing::field::Empty, @@ -571,6 +579,7 @@ impl Store { /// Delete an object by id. #[tracing::instrument( name = "store", + level = "debug", skip_all, fields(otel.name = "store.delete", otel.status_code = tracing::field::Empty, object_type = %object_type, object.id = %id) )] @@ -581,6 +590,7 @@ impl Store { /// Delete objects of one type by id in bounded, set-based statements. #[tracing::instrument( name = "store", + level = "debug", skip_all, fields( otel.name = "store.delete_many", @@ -597,6 +607,7 @@ impl Store { /// Count objects of a given type within a workspace. #[tracing::instrument( name = "store", + level = "debug", skip_all, fields(otel.name = "store.count_in_workspace", otel.status_code = tracing::field::Empty, object_type = %object_type, workspace = %workspace) )] @@ -611,6 +622,7 @@ impl Store { /// Delete all objects of a given type within a workspace. #[tracing::instrument( name = "store", + level = "debug", skip_all, fields(otel.name = "store.delete_all_in_workspace", otel.status_code = tracing::field::Empty, object_type = %object_type, workspace = %workspace) )] @@ -625,6 +637,7 @@ impl Store { /// Delete all objects of a given type with a matching scope. #[tracing::instrument( name = "store", + level = "debug", skip_all, fields(otel.name = "store.delete_by_scope", otel.status_code = tracing::field::Empty, object_type = %object_type, scope = %scope) )] @@ -635,6 +648,7 @@ impl Store { /// Delete an object by name within an object type and workspace. #[tracing::instrument( name = "store", + level = "debug", skip_all, fields(otel.name = "store.delete_by_name", otel.status_code = tracing::field::Empty, object_type = %object_type, workspace = %workspace, object.name = %name) )] @@ -650,6 +664,7 @@ impl Store { /// List objects by type and workspace. #[tracing::instrument( name = "store", + level = "debug", skip_all, fields(otel.name = "store.list", otel.status_code = tracing::field::Empty, object_type = %object_type, workspace = %workspace) )] @@ -666,6 +681,7 @@ impl Store { /// List objects by type across all workspaces. #[tracing::instrument( name = "store", + level = "debug", skip_all, fields(otel.name = "store.list_by_type", otel.status_code = tracing::field::Empty, object_type = %object_type) )] @@ -681,6 +697,7 @@ impl Store { /// List workspace objects after a stable cursor, without offset drift. #[tracing::instrument( name = "store", + level = "debug", skip_all, fields( otel.name = "store.list_after", @@ -702,6 +719,7 @@ impl Store { /// List objects across workspaces after a stable cursor, without offset drift. #[tracing::instrument( name = "store", + level = "debug", skip_all, fields( otel.name = "store.list_by_type_after", @@ -827,6 +845,7 @@ impl Store { /// UUIDs which are globally unique. Revisit if non-UUID scopes are introduced. #[tracing::instrument( name = "store", + level = "debug", skip_all, fields(otel.name = "store.list_by_scope", otel.status_code = tracing::field::Empty, object_type = %object_type, scope = %scope) )] @@ -844,6 +863,7 @@ impl Store { /// Label selector format: "key1=value1,key2=value2" (comma-separated equality matches). #[tracing::instrument( name = "store", + level = "debug", skip_all, fields( otel.name = "store.list_with_selector", otel.status_code = tracing::field::Empty, @@ -912,6 +932,7 @@ impl Store { /// List objects by type across all workspaces with label selector filtering. #[tracing::instrument( name = "store", + level = "debug", skip_all, fields(otel.name = "store.list_all_with_selector", otel.status_code = tracing::field::Empty, object_type = %object_type, label_selector = %label_selector) )] @@ -1310,6 +1331,7 @@ pub fn parse_label_selector(selector: &str) -> PersistenceResult, interval: }); } -#[tracing::instrument( - name = "refresh", - skip_all, - fields( - otel.name = "refresh.provider_credentials", - watched_count = tracing::field::Empty, - due_count = tracing::field::Empty, - ) -)] async fn run_refresh_worker_tick( store: &Store, credentials: Option<&crate::credentials::CredentialRuntime>, compute: Option<&crate::compute::ComputeRuntime>, ) -> Result<(), Status> { let now_ms = current_time_ms(); - let states = list_all_refresh_states(store).await.inspect_err(|_| { - crate::otel_tracing::mark_error(&tracing::Span::current()); - })?; + let states = list_all_refresh_states(store).await?; let watched_count = states.len(); let due_count = states .iter() @@ -1958,13 +1947,44 @@ async fn run_refresh_worker_tick( .iter() .filter(|state| state.status == "rotation_requested") .count(); - let span = tracing::Span::current(); - span.record("watched_count", watched_count); - span.record("due_count", due_count); info!( watched_count, due_count, rotation_requested_count, "provider credential refresh worker sweep" ); + if !states + .iter() + .any(|state| refresh_state_has_work(state, now_ms)) + { + return Ok(()); + } + let span = tracing::info_span!( + "refresh", + otel.name = "refresh.provider_credentials", + watched_count, + due_count, + ); + Box::pin(refresh_states(store, credentials, compute, states, now_ms).instrument(span)).await; + Ok(()) +} + +fn refresh_state_has_work(state: &StoredProviderCredentialRefreshState, now_ms: i64) -> bool { + state + .metadata + .as_ref() + .is_some_and(|metadata| metadata.deletion_time.is_some()) + || !state.pending_secret_deletions.is_empty() + || state.next_refresh_at_ms <= 0 + || state.next_refresh_at_ms <= now_ms + || state.status == "rotation_requested" +} + +async fn refresh_states( + store: &Store, + credentials: Option<&crate::credentials::CredentialRuntime>, + compute: Option<&crate::compute::ComputeRuntime>, + states: Vec, + now_ms: i64, +) { for state in states { if state .metadata @@ -2079,7 +2099,6 @@ async fn run_refresh_worker_tick( ); } } - Ok(()) } #[cfg(test)] @@ -3560,11 +3579,9 @@ mod tests { assert_eq!(credentials.stored_credential_count(), Some(0)); } - /// The worker ticks on a timer with no inbound request, so without a span - /// of its own its store reads export as anonymous single-span traces. #[tokio::test] #[ignore = "flaky under concurrent test execution"] - async fn refresh_worker_ticks_are_roots_and_store_operations_have_parents() { + async fn refresh_worker_records_a_root_span_only_when_a_state_has_work() { use crate::otel_tracing::test_exporter; let store = test_store().await; @@ -3573,27 +3590,38 @@ mod tests { Box::pin(run_refresh_worker_tick(&store, None, None)) .await .unwrap(); + assert!( + traced + .spans_named("refresh.provider_credentials") + .is_empty(), + "an idle tick records no refresh span" + ); - let spans = traced.finished_spans(); - let root = spans - .iter() - .find(|s| s.name == "refresh.provider_credentials") - .unwrap_or_else(|| { - panic!( - "the tick records a span of its own, got {:?}", - spans.iter().map(|s| &s.name).collect::>() - ) - }); - - test_exporter::assert_is_root(root); - let store_span = spans - .iter() - .find(|span| { - span.name.starts_with("store.") - && span.span_context.trace_id() == root.span_context.trace_id() - }) - .expect("the tick records its store operation"); - test_exporter::assert_has_parent(store_span); + let provider = provider("my-external", "outlook"); + store.put_message(&provider).await.unwrap(); + let state = new_refresh_state( + &provider, + "default", + "MS_GRAPH_ACCESS_TOKEN", + NewRefreshStateConfig { + additional_output_keys: HashMap::new(), + strategy: ProviderCredentialRefreshStrategy::External, + material: HashMap::new(), + secret_material_keys: Vec::new(), + expires_at_ms: 0, + token_url: String::new(), + scopes: Vec::new(), + refresh_before: None, + max_lifetime: None, + }, + ) + .unwrap(); + put_refresh_state(&store, &state).await.unwrap(); + + Box::pin(run_refresh_worker_tick(&store, None, None)) + .await + .unwrap(); + test_exporter::assert_is_root(&traced.span_named("refresh.provider_credentials")); } #[test] diff --git a/docs/how-it-works/gateways/configuration.mdx b/docs/how-it-works/gateways/configuration.mdx index 1308871cea..3b3c3aaaf0 100644 --- a/docs/how-it-works/gateways/configuration.mdx +++ b/docs/how-it-works/gateways/configuration.mdx @@ -333,7 +333,7 @@ The OpenTelemetry SDK logs export failures after startup. Spans in a failed batc `service_name` sets the gateway's `service.name` resource attribute and defaults to `openshell-gateway`. The gateway also reports `service.version`, `openshell.gateway.name` from the gateway's configured `name`, and `openshell.gateway.compute_driver`. -Only OpenTelemetry traces are exported. Inbound gRPC and HTTP requests produce server spans named for the RPC or HTTP method. Store and compute-driver operations appear as child spans. Internal reconciliation, credential-refresh, and driver-watch loops create operation roots for their store work because no inbound request supplies a parent. The gateway continues valid W3C `traceparent` context and starts a new trace when none is supplied. Request spans carry `method`, `path`, and the `request_id` that also appears in gateway logs. Health endpoint spans use DEBUG level and are not exported by the default INFO filter. +Only OpenTelemetry traces are exported. Inbound gRPC and HTTP requests produce server spans named for the RPC or HTTP method. Compute-driver operations appear as child spans. Internal reconciliation and driver-watch loops create operation roots because no inbound request supplies a parent, and credential refresh creates one only when a credential is due or needs cleanup. The gateway continues valid W3C `traceparent` context and starts a new trace when none is supplied. Request spans carry `method`, `path`, and the `request_id` that also appears in gateway logs. Store operation spans, health endpoint spans, and spans for RPCs that sandbox supervisors call on a timer (`GetSandboxConfig` and `ReportProviderReadiness`, including its peer-forwarded form) use DEBUG level. The default INFO filter does not export them. Set the gateway log level to `debug` to include them. The gateway forwards the OTLP configuration, configured gateway name, configured compute driver, and W3C trace context to managed external drivers. Built-in drivers also export their spans to the same collector through dedicated in-process providers. Driver spans retain the gateway trace context, use a distinct service name such as `openshell-driver-docker` or `openshell-driver-podman`, and carry the same `openshell.gateway.name` and `openshell.gateway.compute_driver` resource attributes as gateway spans. Compute-driver client and server spans use the same fully qualified protobuf operation name, such as `openshell.compute.v1.ComputeDriver/CreateSandbox`, in both the span name and `rpc.method`. The service name and span kind distinguish each side. Backend-prefixed child spans identify implementation work. A streaming watch records a terminal status when observed; consumer teardown without a terminal status leaves the span status unset. Operator-run external drivers own their own telemetry configuration. From 5c8eedd4bf80d46aa53be7e1dbbc7a601ed43abc Mon Sep 17 00:00:00 2001 From: Emilien Macchi Date: Wed, 30 Sep 2026 13:50:34 -0400 Subject: [PATCH 03/33] CARRY: feat(konflux): add OpenClaw reference harness image Add odh-openshell-openclaw, an unsupported reference image that runs the OpenClaw agent harness in an OpenShell sandbox. It installs OpenClaw 2026.9.5 on ubi9/nodejs-24-minimal from the same npm lockfile as the AIPCC agentic OpenClaw image, built from a hermetic npm and rpm prefetch. The only RPM is crypto-policies-scripts, for DEFAULT:PQ. OpenClaw sits world-readable under /usr/local, which the default Landlock policy already covers, and the image runs as 1000:1000 with no ENTRYPOINT and no baked policy. The openclaw-start wrapper onboards OpenClaw on first run against an OpenAI-compatible endpoint from MODEL_BASE_URL, MODEL_ID and a provider-injected CUSTOM_API_KEY, then starts it. An example provider profile, a glibc ceiling lint, a smoke test and a README sit next to the prefetch configs. build-local.sh gains a per-component prefetch input and an openclaw target that restores the npm files Hermeto rewrites; the existing components build as before. renovate.json gains a regex manager so MintMaker bumps the nodejs-24-minimal digest; the npm inputs stay untracked on purpose and follow the AIPCC agentic image. The Tekton pipelines follow once the Konflux component is registered in odh-konflux-central. Related: RHAISTRAT-1845 Signed-off-by: Emilien Macchi --- deploy/docker/Dockerfile.konflux.openclaw | 115 + deploy/konflux/build-local.sh | 55 +- deploy/konflux/openclaw/README.md | 160 + deploy/konflux/openclaw/check-glibc.sh | 49 + .../openclaw/model-provider-profile.yaml | 46 + deploy/konflux/openclaw/openclaw-start.sh | 85 + deploy/konflux/openclaw/package-lock.json | 4713 +++++++++++++++++ deploy/konflux/openclaw/package.json | 17 + deploy/konflux/openclaw/profile.sh | 17 + deploy/konflux/openclaw/rpms.in.yaml | 40 + deploy/konflux/openclaw/rpms.lock.yaml | 726 +++ deploy/konflux/openclaw/smoke-test.sh | 318 ++ renovate.json | 11 +- 13 files changed, 6342 insertions(+), 10 deletions(-) create mode 100644 deploy/docker/Dockerfile.konflux.openclaw create mode 100644 deploy/konflux/openclaw/README.md create mode 100755 deploy/konflux/openclaw/check-glibc.sh create mode 100644 deploy/konflux/openclaw/model-provider-profile.yaml create mode 100755 deploy/konflux/openclaw/openclaw-start.sh create mode 100644 deploy/konflux/openclaw/package-lock.json create mode 100644 deploy/konflux/openclaw/package.json create mode 100755 deploy/konflux/openclaw/profile.sh create mode 100644 deploy/konflux/openclaw/rpms.in.yaml create mode 100644 deploy/konflux/openclaw/rpms.lock.yaml create mode 100755 deploy/konflux/openclaw/smoke-test.sh diff --git a/deploy/docker/Dockerfile.konflux.openclaw b/deploy/docker/Dockerfile.konflux.openclaw new file mode 100644 index 0000000000..dc535ec095 --- /dev/null +++ b/deploy/docker/Dockerfile.konflux.openclaw @@ -0,0 +1,115 @@ +# syntax=docker/dockerfile:1.4 + +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# OpenClaw reference harness image for OpenShell sandboxes (hermetic build). +# +# This is an unsupported reference composition, not RHOAI product content. It +# runs the OpenClaw agent (https://github.com/openclaw/openclaw) inside an +# OpenShell sandbox. See deploy/konflux/openclaw/README.md for scope and +# verification status. +# +# Requires prefetched dependencies via Hermeto (npm, rpm). The npm inputs +# (package.json, package-lock.json) are a byte-identical copy of Red Hat's +# internal AIPCC agentic OpenClaw image's npm closure, so both images share +# one npm lineage; this image runs it on UBI Node 24 instead of Node 26. The +# only RPM is crypto-policies-scripts, which keeps this repo's DEFAULT:PQ +# convention. See deploy/konflux/openclaw/ for prefetch configs. +# +# Local build: ./deploy/konflux/build-local.sh openclaw +# +# No RUN heredocs and no RUN --mount: build-local.sh sed-injects +# `. /cachi2/cachi2.env &&` after every `RUN ` to source the hermetic +# environment, so every RUN must start at column 0 with `RUN ` and use +# `&& \` continuations only. + +# Both stages use the same base and OpenClaw version; bump them only here. +ARG NODEJS_IMAGE=registry.access.redhat.com/ubi9/nodejs-24-minimal:9.8@sha256:9785f7415bff723e0dfcb1b928d81a06bf004b6a1e8a941bd08983e61b577b46 +ARG OPENCLAW_VERSION=2026.9.5 + +# --------------------------------------------------------------------------- +# Build stage: install OpenClaw from the prefetched npm closure +# --------------------------------------------------------------------------- +FROM ${NODEJS_IMAGE} AS builder + +ARG OPENCLAW_VERSION +ARG GLIBC_MAX=2.34 + +USER 0 + +COPY deploy/konflux/openclaw/package.json deploy/konflux/openclaw/package-lock.json /usr/local/lib/openclaw/ +COPY deploy/konflux/openclaw/check-glibc.sh /usr/local/libexec/check-glibc.sh + +# Install strictly from the prefetched closure (--offline), refuse any +# lifecycle script not explicitly allow-listed in package.json +# (--strict-allow-scripts), confirm the pinned OpenClaw version landed, gate +# on the glibc ceiling of any native addon, then make the tree world-readable +# so it works from any container UID (the final stage runs as 1000:1000). +RUN cd /usr/local/lib/openclaw && \ + npm ci --offline --strict-allow-scripts --no-audit --no-fund --cache /tmp/npm-cache && \ + rm -rf /tmp/npm-cache && \ + test "$(node -p 'require("./node_modules/openclaw/package.json").version')" = "${OPENCLAW_VERSION}" && \ + bash /usr/local/libexec/check-glibc.sh /usr/local/lib/openclaw/node_modules "${GLIBC_MAX}" && \ + chmod -R a+rX /usr/local/lib/openclaw && \ + test -z "$(find /usr/local/lib/openclaw ! -perm -o=r -print -quit)" + +# --------------------------------------------------------------------------- +# Runtime +# --------------------------------------------------------------------------- +FROM ${NODEJS_IMAGE} + +ARG OPENCLAW_VERSION + +USER 0 + +RUN microdnf install -y --nodocs --setopt=install_weak_deps=0 \ + crypto-policies-scripts \ + && microdnf clean all + +# Enable post-quantum cryptography support in the RHEL system policy. Node's +# shared OpenSSL 3.5.8 reads this policy, so a TLS 1.3 handshake negotiates +# X25519MLKEM768 instead of X25519. +RUN update-crypto-policies --set DEFAULT:PQ + +RUN groupadd -g 1000 sandbox && useradd -u 1000 -g 1000 -M -d /sandbox -s /bin/bash sandbox + +COPY --from=builder /usr/local/lib/openclaw /usr/local/lib/openclaw +RUN ln -s /usr/local/lib/openclaw/node_modules/openclaw/openclaw.mjs /usr/local/bin/openclaw + +COPY --chmod=0755 deploy/konflux/openclaw/openclaw-start.sh /usr/local/bin/openclaw-start +COPY --chmod=0644 deploy/konflux/openclaw/profile.sh /etc/profile.d/openclaw.sh + +ENV HOME=/sandbox \ + PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ + NPM_CONFIG_PREFIX=/sandbox/.npm-global \ + OPENCLAW_NO_AUTO_UPDATE=1 \ + DO_NOT_TRACK=1 \ + OPENCLAW_DISABLE_BONJOUR=1 \ + OPENCLAW_OFFLINE=1 + +LABEL com.redhat.component="odh-openshell-openclaw-container" \ + name="opendatahub/odh-openshell-openclaw" \ + version="${OPENCLAW_VERSION}" \ + summary="OpenClaw agent harness reference image for OpenShell sandboxes" \ + description="Unsupported reference composition that runs the OpenClaw agent harness inside an OpenShell sandbox; not RHOAI product content" \ + maintainer="['managed-open-data-hub@redhat.com']" \ + io.k8s.display-name="odh-openshell-openclaw" \ + io.k8s.description="odh-openshell-openclaw" \ + io.openshift.expose-services="" \ + io.openshift.tags="openshell,sandbox,openclaw,agent" \ + help="See the README for usage: https://github.com/opendatahub-io/openshell/blob/main/deploy/konflux/openclaw/README.md" \ + url="https://github.com/opendatahub-io/openshell/blob/main/deploy/konflux/openclaw/README.md" \ + io.openshell.sandbox.harness="openclaw" \ + io.openshell.harness.version="${OPENCLAW_VERSION}" \ + io.openshift.s2i.scripts-url="" \ + io.s2i.scripts-url="" \ + com.redhat.deployments-dir="" \ + com.redhat.dev-mode="" \ + com.redhat.dev-mode.port="" + +WORKDIR / + +USER 1000:1000 + +CMD ["/usr/local/bin/openclaw-start"] diff --git a/deploy/konflux/build-local.sh b/deploy/konflux/build-local.sh index 24669841b5..0126adb7f5 100755 --- a/deploy/konflux/build-local.sh +++ b/deploy/konflux/build-local.sh @@ -10,8 +10,11 @@ # ./deploy/konflux/build-local.sh gateway # ./deploy/konflux/build-local.sh supervisor # ./deploy/konflux/build-local.sh sandbox +# ./deploy/konflux/build-local.sh openclaw # ./deploy/konflux/build-local.sh all # +# The openclaw smoke test additionally requires host openssl. +# # Override architecture (default: host arch via uname -m): # PLATFORM=linux/arm64 ./deploy/konflux/build-local.sh supervisor set -euo pipefail @@ -29,7 +32,12 @@ esac PLATFORM="${PLATFORM:-${DEFAULT_PLATFORM}}" CLEANUP_PATHS=() +RESTORE_FILES=() cleanup() { + local i + for ((i = 0; i < ${#RESTORE_FILES[@]}; i += 2)); do + cp -p "${RESTORE_FILES[i]}" "${RESTORE_FILES[i+1]}" + done for p in "${CLEANUP_PATHS[@]}"; do rm -rf "$p" done @@ -39,8 +47,9 @@ trap cleanup EXIT build_image() { local component="$1" - local dockerfile konfig_dir output_dir repos_dir + local dockerfile konfig_dir output_dir repos_dir prefetch_input + prefetch_input="" case "$component" in gateway) dockerfile="deploy/docker/Dockerfile.konflux.gateway" @@ -58,26 +67,48 @@ build_image() { dockerfile="deploy/docker/Dockerfile.konflux.cli" konfig_dir="deploy/konflux/cli" ;; + openclaw) + dockerfile="deploy/docker/Dockerfile.konflux.openclaw" + konfig_dir="deploy/konflux/openclaw" + prefetch_input="[ + {\"path\": \"${konfig_dir}\", \"type\": \"npm\"}, + {\"path\": \"${konfig_dir}\", \"type\": \"rpm\"} + ]" + ;; *) echo "Unknown component: $component" >&2 exit 1 ;; esac + if [[ -z "${prefetch_input}" ]]; then + prefetch_input="[ + {\"path\": \".\", \"type\": \"cargo\"}, + {\"path\": \"${konfig_dir}\", \"type\": \"rpm\"}, + {\"path\": \"${konfig_dir}\", \"type\": \"generic\", \"lockfile\": \"generic-fetcher.yaml\"} + ]" + fi + output_dir="${OUTPUT_DIR}/${component}" repos_dir=$(mktemp -d) CLEANUP_PATHS+=("${repos_dir}") + if [[ -f "${REPO_ROOT}/${konfig_dir}/package-lock.json" ]]; then + local npm_backup + npm_backup=$(mktemp -d) + CLEANUP_PATHS+=("${npm_backup}") + cp -p "${REPO_ROOT}/${konfig_dir}/package.json" "${npm_backup}/package.json" + cp -p "${REPO_ROOT}/${konfig_dir}/package-lock.json" "${npm_backup}/package-lock.json" + RESTORE_FILES+=("${npm_backup}/package.json" "${REPO_ROOT}/${konfig_dir}/package.json") + RESTORE_FILES+=("${npm_backup}/package-lock.json" "${REPO_ROOT}/${konfig_dir}/package-lock.json") + fi + echo "=== Prefetching ${component} dependencies ===" rm -rf "${output_dir}" hermeto fetch-deps \ --source "${REPO_ROOT}" \ --output "${output_dir}" \ - "[ - {\"path\": \".\", \"type\": \"cargo\"}, - {\"path\": \"${konfig_dir}\", \"type\": \"rpm\"}, - {\"path\": \"${konfig_dir}\", \"type\": \"generic\", \"lockfile\": \"generic-fetcher.yaml\"} - ]" + "${prefetch_input}" echo "=== Injecting files ===" hermeto inject-files "${output_dir}" --for-output-dir /cachi2/output @@ -132,15 +163,20 @@ build_image() { echo "=== ${component} built successfully ===" # The sandbox runtime image is ubi-micro without crypto-policies. - if [[ "${component}" != "sandbox" ]]; then + # openclaw stays on UBI 9 with DEFAULT:PQ; smoke-test.sh asserts it. + if [[ "${component}" != "sandbox" && "${component}" != "openclaw" ]]; then test "$(podman run --rm --user=0 --entrypoint /usr/bin/update-crypto-policies "openshell-${component}-konflux" --show)" = "DEFAULT:PQ" fi - podman run --rm --platform "${PLATFORM}" "openshell-${component}-konflux" --help 2>&1 | head -3 + if [[ "${component}" == "openclaw" ]]; then + PLATFORM="${PLATFORM}" bash "${REPO_ROOT}/deploy/konflux/openclaw/smoke-test.sh" "openshell-${component}-konflux" + else + podman run --rm --platform "${PLATFORM}" "openshell-${component}-konflux" --help 2>&1 | head -3 + fi echo "" } if [[ $# -eq 0 ]]; then - echo "Usage: $0 {gateway|supervisor|sandbox|cli|all}" >&2 + echo "Usage: $0 {gateway|supervisor|sandbox|cli|openclaw|all}" >&2 exit 1 fi @@ -150,6 +186,7 @@ if [[ "$target" == "all" ]]; then build_image supervisor build_image sandbox build_image cli + build_image openclaw else build_image "$target" fi diff --git a/deploy/konflux/openclaw/README.md b/deploy/konflux/openclaw/README.md new file mode 100644 index 0000000000..cf98278166 --- /dev/null +++ b/deploy/konflux/openclaw/README.md @@ -0,0 +1,160 @@ +# OpenClaw reference harness image + +`odh-openshell-openclaw` runs the [OpenClaw](https://github.com/openclaw/openclaw) agent harness inside an OpenShell sandbox. It is built from `deploy/docker/Dockerfile.konflux.openclaw` and the files in this directory. + +## Status + +This image is an unsupported reference composition. It is published only on quay.io/opendatahub, it is not RHOAI product content, and it is not the default sandbox workload image. It targets midstream main 23ac1e1c2 (v0.1.2-rhaiv.2). Read [Verified vs not verified](#verified-vs-not-verified) before relying on it. + +## What's inside + +- Base: `registry.access.redhat.com/ubi9/nodejs-24-minimal:9.8`, pinned by digest (Node 24.19.0, npm 11.17.0), for amd64 and arm64. +- OpenClaw 2026.9.5, installed world-readable under `/usr/local/lib/openclaw`, plus `/usr/local/bin/openclaw` and the `openclaw-start` wrapper. The npm closure is the same one Red Hat's internal AIPCC agentic OpenClaw image uses; that image runs it on Node 26. +- User 1000:1000 (`sandbox`), `HOME=/sandbox`, `WORKDIR /`, no `/sandbox` directory, no ENTRYPOINT and no baked sandbox policy. OpenShell creates and owns the workspace. +- The `DEFAULT:PQ` system crypto policy, so Node's TLS 1.3 prefers X25519MLKEM768. `python3` is present as a dependency of `crypto-policies-scripts`. +- OpenClaw defaults `OPENCLAW_NO_AUTO_UPDATE=1`, `DO_NOT_TRACK=1`, `OPENCLAW_DISABLE_BONJOUR=1` and `OPENCLAW_OFFLINE=1`, set in the image env and again in `/etc/profile.d/openclaw.sh` for login shells. Inherited base env such as `APP_ROOT` or `NPM_RUN` is harmless. +- Not included: git, gzip, ps, Chromium and chat-channel plugins. + +## Build locally + +`build-local.sh` fetches the npm and rpm closure with Hermeto, builds with `--network none`, restores `package.json` and `package-lock.json` after Hermeto rewrites them, and runs `smoke-test.sh`. The smoke test needs `openssl` on the host. + +```shell +./deploy/konflux/build-local.sh openclaw +PLATFORM=linux/arm64 ./deploy/konflux/build-local.sh openclaw +``` + +`PLATFORM` accepts `linux/amd64`, `linux/x86_64`, `linux/arm64` and `linux/aarch64`. + +Without a Hermeto install, put a `hermeto` shim on `PATH` that runs `uvx --from git+https://github.com/hermetoproject/hermeto.git hermeto "$@"`. Hosts whose EDR agent kills OpenClaw can't run this build (see [Known limitations](#known-limitations)). + +## Run with OpenShell + +Copy `model-provider-profile.yaml`, set `host` and `port` to your model Service, then lint and import it and create the provider: + +```shell +openshell profile lint -f model-provider-profile.yaml +openshell profile import -f model-provider-profile.yaml --global +openshell provider create --name model --type openclaw-model --credential CUSTOM_API_KEY= +``` + +Create an interactive sandbox. On first run, `openclaw-start` onboards OpenClaw from the environment and then opens the TUI: + +```shell +openshell sandbox create --name openclaw \ + --from quay.io/opendatahub/odh-openshell-openclaw@sha256: \ + --provider model \ + --env MODEL_BASE_URL=http://..svc.cluster.local:8000/v1 \ + --env MODEL_ID= \ + --tty -- openclaw-start +``` + +For headless use, onboard in a detached sandbox and run one-shot turns with `agent exec`: + +```shell +openshell sandbox create --name openclaw \ + --from quay.io/opendatahub/odh-openshell-openclaw@sha256: \ + --provider model \ + --env MODEL_BASE_URL=http://..svc.cluster.local:8000/v1 \ + --env MODEL_ID= \ + --detach -- bash -lc 'openclaw-start --version && exec sleep infinity' +openshell sandbox exec --name openclaw -- openclaw-start agent exec "Summarize README.md" +``` + +| Variable | Purpose | +| --- | --- | +| `MODEL_BASE_URL` | OpenAI-compatible base URL. It must use exactly the host and port from the provider profile. | +| `MODEL_ID` | Model name served at that URL. | +| `CUSTOM_API_KEY` | Injected by the provider as a placeholder. For an endpoint without auth, create the provider with `--credential CUSTOM_API_KEY=unused`; the provider also supplies the egress rule for the model endpoint. | +| `OPENCLAW_*`, `DO_NOT_TRACK` | Update, telemetry, Bonjour and download defaults. Override them with `--env`. | + +- `openclaw-start` onboards only when `~/.openclaw/openclaw.json` is missing. Delete that file and its `.bak` to onboard again. Deleting `~/.openclaw` resets all OpenClaw state. +- Start OpenClaw through `openclaw-start` or a login-shell exec. `sandbox exec --no-login-shell` skips the profile.d defaults. +- With no arguments, `openclaw-start` runs `openclaw tui --local`, which needs a TTY, so don't combine it with `--detach`. When the TUI exits, the sandbox is Completed. `openshell sandbox start openclaw` restarts it with its state intact. +- Set `contextWindow` and `maxTokens` for the model under `models.providers.openshell-model` with `openclaw config set` so they match your server. + +## Policy + +The attached provider profile is all the image needs: it allows `/usr/bin/node` to reach the model endpoint, and the default filesystem policy already covers `/usr` and `/etc`. Without the provider, add the model host:port yourself with `openshell policy update --add-endpoint ::read-write:rest:enforce --binary /usr/bin/node`, or every model request is denied. Add other egress per sandbox with `openshell policy update`. Don't bake `/etc/openshell/policy.yaml` into a derived image unless it is schema-valid. Exec tools in pty mode need `/dev/ptmx`, which the default Landlock set does not grant. + +## Control UI + +Best effort, not validated. Onboarding configures OpenClaw's gateway on loopback port 18789 with token auth. Run `openclaw-start gateway` as the sandbox command instead of the TUI, expose it with `openshell service expose openclaw 18789`, add the service URL's origin to `gateway.controlUi.allowedOrigins`, and approve the browser with `openclaw devices approve`. + +## Known limitations + +- OpenShell sandboxes need OCP 4.19 or later (Landlock ABI 3 or newer). +- On RHCOS 9 the sandbox runs in legacy read-only mode. OpenClaw's gateway then treats every client as remote, so the Control UI always needs device approval, and Python or Go servers in the sandbox fail at `accept()`. OpenClaw's embedded MCP loopback server should still work. +- EDR agents such as CrowdStrike Falcon may kill OpenClaw entirely, not only its sqlite workers. That includes image builds, where any step touching the OpenClaw package path can be killed. Build on sanctioned infrastructure without such a policy, such as Konflux, and run `smoke-test.sh` on a host or CI runner without it. Nodes running such an agent may kill OpenClaw inside sandboxes too. +- OpenClaw calls `os.userInfo()` unguarded in several paths, so an SCC-assigned UID with no passwd entry may break status, session or TUI commands. This is unverified. +- `web_fetch` rejects OpenShell's synthetic DNS answers (198.18.0.0/15, fc00::/7) unless `tools.web.fetch.ssrfPolicy.allowRfc2544BenchmarkRange` and `tools.web.fetch.ssrfPolicy.allowIpv6UniqueLocalRange` are set, per OpenClaw's `docs/tools/web.md`. This is unverified. +- Updating the image never touches OpenClaw state on the sandbox's persistent volume. + +## Verified vs not verified + +Verified: + +- [x] `build-local.sh openclaw` on GitHub-hosted Ubuntu 24.04 runners (podman 4.9.3), amd64 and native arm64: the hermetic build succeeds, and every `smoke-test.sh` check passes on both arches. That covers arch, config, labels, `openclaw --version`, layout, `DEFAULT:PQ` with Node negotiating X25519MLKEM768, first-run onboarding and its idempotence as UID 51234 with no passwd entry on a read-only rootfs, the login-shell env, and the missing-env exit. +- [x] Host unit tests for `openclaw-start` (with a fake OpenClaw CLI), `profile.sh` and `check-glibc.sh`. +- [x] `build-local.sh`: a stub trace shows the four existing components build unchanged, and the npm files are restored even when the build fails. +- [x] The glibc ceiling lint fails the build at 2.33 (`@openclaw/fs-safe-linux-x64-gnu` needs 2.34). +- [x] Trivy config scan of the Dockerfile is clean, and the license check adds only `rpms.lock.yaml`. + +Not verified: + +- [ ] Any OpenShell sandbox run, including provider credential injection and `openshell profile lint` of the example profile. +- [ ] Kubernetes and OpenShift (PVC seeding with no `/sandbox`, SCC arbitrary UID, exec env path) and RHCOS 9 legacy mode. +- [ ] Agent turns, the TUI and the Control UI. +- [ ] `os.userInfo()` without a passwd entry. + +Once the component is onboarded, the Konflux PR pipeline is the first full build of this Dockerfile on both arches. It runs the in-build checks (the hermetic npm and rpm install, the OpenClaw version pin check, which only reads `package.json`, the glibc ceiling lint and the permissions check) plus Konflux's scans. It does not run `smoke-test.sh`, so after each OpenClaw or base bump, re-run `build-local.sh openclaw` on a host or CI runner without such an EDR policy, or add a Konflux IntegrationTestScenario that runs `smoke-test.sh` against the built image. + +## Disconnected + +Mirror the image by digest with oc-mirror, then point `MODEL_BASE_URL` at an in-cluster model server: + +```yaml +kind: ImageSetConfiguration +apiVersion: mirror.openshift.io/v2alpha1 +mirror: + additionalImages: + - name: quay.io/opendatahub/odh-openshell-openclaw@sha256: +``` + +## Build your own on UBI + +There is no branded OpenShell base image for RHOAI 3.6. To package another harness, start from stock UBI and keep the same contract: + +```dockerfile +FROM registry.access.redhat.com/ubi9/ubi-minimal:9.8@sha256:7fbeae18dc9476399f565e68255f602a3374ea8614ba3d14843565131a13ff93 +USER 0 +RUN microdnf install -y --nodocs shadow-utils && microdnf clean all && \ + groupadd -g 1000 sandbox && useradd -u 1000 -g 1000 -M -d /sandbox -s /bin/bash sandbox +COPY --chmod=0755 my-harness /usr/local/bin/my-harness +WORKDIR / +USER 1000:1000 +``` + +- Use the registry.redhat.io digest your mirror carries if you don't pull from registry.access.redhat.com. +- Install tools world-readable under `/usr` or `/usr/local`, and put nothing under `/sandbox`, which OpenShell owns. +- Keep bash. OpenShell runs exec sessions through a bash login shell. +- Optionally install `crypto-policies-scripts` and run `update-crypto-policies --set DEFAULT:PQ`. +- Don't ship `/etc/openshell/policy.yaml` unless it is schema-valid. + +## Updating OpenClaw + +The npm inputs come from AIPCC agentic commit dc756bc3 (OpenClaw 2026.9.5). AIPCC has since moved to 2026.9.6, and a follow-up bump is expected. + +1. Copy `package.json` and `package-lock.json` byte-identically from the AIPCC agentic repo (Red Hat internal), and record the source commit. +2. Review the version-pinned `allowScripts` entries in `package.json`. +3. Bump `ARG OPENCLAW_VERSION` at the top of the Dockerfile. +4. Leave the base alone: MintMaker (Renovate) bumps the `ARG NODEJS_IMAGE` digest through a regex manager in `renovate.json` and refreshes `rpms.lock.yaml`. It deliberately does not track the npm inputs, which follow AIPCC. +5. Rebuild both arches. The glibc lint fails the build if a native addon needs more than GLIBC 2.34, and x86_64 has no headroom today. + +## Konflux + +The Tekton pipelines follow once the component is registered in odh-konflux-central. The build is hermetic for linux/x86_64 and linux/arm64 with this prefetch input: + +```json +[{"type": "npm", "path": "deploy/konflux/openclaw"}, {"type": "rpm", "path": "deploy/konflux/openclaw"}] +``` diff --git a/deploy/konflux/openclaw/check-glibc.sh b/deploy/konflux/openclaw/check-glibc.sh new file mode 100755 index 0000000000..0571c0ed2c --- /dev/null +++ b/deploy/konflux/openclaw/check-glibc.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +usage() { + echo "Usage: check-glibc.sh [max=2.34]" >&2 +} + +if [[ $# -lt 1 || $# -gt 2 ]]; then + usage + exit 2 +fi + +dir=$1 +max=${2:-2.34} + +elf_count=0 +offending=0 + +while IFS= read -r -d '' file; do + magic=$(head -c4 "$file" | od -An -tx1) + if [[ "$magic" != " 7f 45 4c 46" ]]; then + continue + fi + elf_count=$((elf_count + 1)) + + # grep returns 1 (no match) for a file with no GLIBC_* symbol versions at + # all, e.g. a musl-linked .node/.so; under pipefail that would otherwise + # abort the script here instead of falling through to the empty-need skip. + need=$(grep -aoE 'GLIBC_[0-9]+\.[0-9]+(\.[0-9]+)?' "$file" | sed 's/^GLIBC_//' | sort -uV | tail -n1) || true + if [[ -z "$need" ]]; then + continue + fi + + highest=$(printf '%s\n%s\n' "$max" "$need" | sort -V | tail -n1) + if [[ "$highest" == "$need" && "$need" != "$max" ]]; then + echo "check-glibc: ${file} needs GLIBC_${need} > ${max}" + offending=$((offending + 1)) + fi +done < <(find "$dir" -type f \( -name '*.node' -o -name '*.so' -o -name '*.so.*' \) -print0) + +if [[ ${offending} -gt 0 ]]; then + exit 1 +fi + +echo "check-glibc: ok (${elf_count} ELF files, max ${max})" diff --git a/deploy/konflux/openclaw/model-provider-profile.yaml b/deploy/konflux/openclaw/model-provider-profile.yaml new file mode 100644 index 0000000000..c3efc81e3c --- /dev/null +++ b/deploy/konflux/openclaw/model-provider-profile.yaml @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Example provider profile for the OpenClaw reference image +# (odh-openshell-openclaw). OpenShell does not load it; copy it, edit it and +# import it explicitly: +# openshell profile lint -f model-provider-profile.yaml +# openshell profile import -f model-provider-profile.yaml --global +# openshell provider create --name model --type openclaw-model \ +# --credential CUSTOM_API_KEY= +# +# Set `host` and `port` to the model Service exactly as MODEL_BASE_URL names +# it (for example http://vllm.models.svc.cluster.local:8000/v1). The proxy +# matches on host:port, so a different spelling of the same Service does not +# get the credential. +# +# Client binaries: node (OpenClaw runs on the image's Node.js). +# Reference layout: /usr/bin/node in odh-openshell-openclaw. +# Credential scope: CUSTOM_API_KEY, sent as a bearer authorization header to +# the host:port below and nowhere else. openclaw-start +# stores it in the OpenClaw config as an env reference, so +# OpenClaw only ever sees the provider placeholder. +# Endpoint access: one OpenAI-compatible endpoint, read-write, L7 enforced. +# Smoke test: see deploy/konflux/openclaw/README.md. + +id: openclaw-model +display_name: OpenClaw model endpoint +description: OpenAI-compatible model endpoint used by the OpenClaw reference image +category: inference +inference_capable: true +credentials: + - name: api_key + description: Bearer token for the model endpoint + env_vars: [CUSTOM_API_KEY] + required: true + auth_style: bearer + header_name: authorization +discovery: + credentials: [api_key] +endpoints: + - host: model.example.svc.cluster.local + port: 8000 + protocol: rest + access: read-write + enforcement: enforce +binaries: [/usr/bin/node] diff --git a/deploy/konflux/openclaw/openclaw-start.sh b/deploy/konflux/openclaw/openclaw-start.sh new file mode 100755 index 0000000000..71b89ac20a --- /dev/null +++ b/deploy/konflux/openclaw/openclaw-start.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +# shellcheck source=/dev/null +if [[ -r /etc/profile.d/openclaw.sh ]]; then + . /etc/profile.d/openclaw.sh +fi + +if [[ -n "${OPENCLAW_PROFILE:-}" ]]; then + echo "openclaw-start: OPENCLAW_PROFILE is not supported" >&2 + exit 2 +fi + +config="${OPENCLAW_CONFIG_PATH:-${OPENCLAW_STATE_DIR:-${OPENCLAW_HOME:-$HOME}/.openclaw}/openclaw.json}" + +if [[ ! -e "${config}" ]]; then + missing=() + if [[ -z "${MODEL_BASE_URL:-}" ]]; then + missing+=("MODEL_BASE_URL") + fi + if [[ -z "${MODEL_ID:-}" ]]; then + missing+=("MODEL_ID") + fi + if [[ -z "${CUSTOM_API_KEY:-}" ]]; then + missing+=("CUSTOM_API_KEY") + fi + + if [[ ${#missing[@]} -gt 0 ]]; then + names="" + for name in "${missing[@]}"; do + if [[ -z "${names}" ]]; then + names="${name}" + else + names="${names}, ${name}" + fi + done + echo "openclaw-start: no OpenClaw config at ${config}; set ${names} for first-run setup" >&2 + for name in "${missing[@]}"; do + if [[ "${name}" == "CUSTOM_API_KEY" ]]; then + echo "openclaw-start: attach an OpenShell provider of type openclaw-model (see model-provider-profile.yaml); it injects CUSTOM_API_KEY and allows /usr/bin/node to reach the model host:port. For an endpoint without auth, create it with --credential CUSTOM_API_KEY=unused" >&2 + break + fi + done + exit 2 + fi + + if ! out=$(openclaw onboard \ + --non-interactive \ + --accept-risk \ + --mode local \ + --auth-choice custom-api-key \ + --custom-base-url "$MODEL_BASE_URL" \ + --custom-model-id "$MODEL_ID" \ + --custom-compatibility openai \ + --custom-provider-id openshell-model \ + --custom-text-input \ + --secret-input-mode ref \ + --gateway-bind loopback \ + --gateway-auth token \ + --gateway-port 18789 \ + --skip-channels \ + --skip-daemon \ + --skip-health \ + --skip-search \ + --skip-skills \ + --skip-ui \ + --skip-hooks \ + --json 2>&1); then + printf '%s\n' "${out}" >&2 + echo "openclaw-start: onboarding failed" >&2 + exit 1 + fi + + echo "openclaw-start: wrote ${config} (provider openshell-model, model ${MODEL_ID})" >&2 +fi + +if (($# == 0)); then + set -- tui --local +fi + +exec openclaw "$@" diff --git a/deploy/konflux/openclaw/package-lock.json b/deploy/konflux/openclaw/package-lock.json new file mode 100644 index 0000000000..f54d16ed10 --- /dev/null +++ b/deploy/konflux/openclaw/package-lock.json @@ -0,0 +1,4713 @@ +{ + "name": "agentic-openclaw-prefetch", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agentic-openclaw-prefetch", + "dependencies": { + "openclaw": "2026.9.5" + } + }, + "node_modules/@agentclientprotocol/sdk": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.4.0.tgz", + "integrity": "sha512-/eufudw+aFY1LKLolT6yFE6UMmYRl7fMJ/DEONSIyR6wI3slHWITBsANRGqXEY8FRzqUxwh7QEaGiZHcJPVThg==", + "license": "Apache-2.0", + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.124.0.tgz", + "integrity": "sha512-cN5O8i9UVxHeOQAzj/XjshWXG8KiibJDw9OGpH2Z/eR3n/RBxdoLxDJOcfqAJWvjaMDFfHTBADU04hWRJVkDyA==", + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1", + "standardwebhooks": "^1.0.0" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@clack/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.5.0.tgz", + "integrity": "sha512-zNikCcd8BbcEvzzG1sbXFrRHFk5kHPrpwZwksPvf9qyQO1Teb7JaXaOAxXZei9nZLDW0gaZawiuTCji88bTBhw==", + "license": "MIT", + "dependencies": { + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@clack/prompts": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.8.0.tgz", + "integrity": "sha512-PXzLZ8N34rxmuo4dJg3xtOXhcBse94qGjDqsteoEYrFrrZ5FSjIGwMAuOcv64ln8rHVBBD06XeVGr+/JX+plcA==", + "license": "MIT", + "dependencies": { + "@clack/core": "1.5.0", + "fast-string-width": "^3.0.2", + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@earendil-works/pi-tui": { + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.85.1.tgz", + "integrity": "sha512-OIzw9efInmO4WOBnD4TxcTdBjmzvYJpzslkgoUro946nEGoYWg5rwv1p4fDt3/JvMx9QybryUCUwlm7j8Dreig==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "1.6.0", + "marked": "18.0.5" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@google/genai": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-2.21.0.tgz", + "integrity": "sha512-+PDtco2/Z0ONdzCGekCoCT+O1VJS9xJQNN4XzQpXG/t3El/SWWMkCWlFRO1KmivOHPa4Q0VjUYu1HBKCZ/v33Q==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@grammyjs/runner": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@grammyjs/runner/-/runner-2.0.3.tgz", + "integrity": "sha512-nckmTs1dPWfVQteK9cxqxzE+0m1VRvluLWB8UgFzsjg62w3qthPJt0TYtJBEdG7OedvfQq4vnFAyE6iaMkR42A==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0" + }, + "engines": { + "node": ">=12.20.0 || >=14.13.1" + }, + "peerDependencies": { + "grammy": "^1.13.1" + } + }, + "node_modules/@grammyjs/transformer-throttler": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@grammyjs/transformer-throttler/-/transformer-throttler-1.2.1.tgz", + "integrity": "sha512-CpWB0F3rJdUiKsq7826QhQsxbZi4wqfz1ccKX+fr+AOC+o8K7ZvS+wqX0suSu1QCsyUq2MDpNiKhyL2ZOJUS4w==", + "license": "MIT", + "dependencies": { + "bottleneck": "^2.0.0" + }, + "engines": { + "node": "^12.20.0 || >=14.13.1" + }, + "peerDependencies": { + "grammy": "^1.0.0" + } + }, + "node_modules/@grammyjs/types": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-5.0.0.tgz", + "integrity": "sha512-iq1Qrq1iPKkB8yAa0qSuIURMZOCuqTY5pWy5gHpCeL1oQ+GPadGhw/cDTVE8waJwuCzacUzuIjRv1sESvk7u7A==", + "license": "MIT" + }, + "node_modules/@homebridge/ciao": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@homebridge/ciao/-/ciao-1.3.12.tgz", + "integrity": "sha512-84/0u0kqKy4qcKupIDaTtbkmk+3qFDuMzxTC0NPfVlHcUT+jHGdP+QCqwEw94/GJxRezDeBLwxaclkBZgCdeUg==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "fast-deep-equal": "^3.1.3", + "source-map-support": "^0.5.21", + "tslib": "^2.8.1" + }, + "bin": { + "ciao-bcs": "lib/bonjour-conformance-testing.js" + } + }, + "node_modules/@hono/node-server": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.1.tgz", + "integrity": "sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@koromix/koffi-android-arm64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-android-arm64/-/koffi-android-arm64-3.2.1.tgz", + "integrity": "sha512-1pJQ4jnZlUJduK9u9DC5CGy3aOgDUPvIXpNb6syV3+Dh5Q/ugezAIGCqvY+w+1mgXsve0pd0NVvJRjdZNHQ6MA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@koromix/koffi-android-x64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-android-x64/-/koffi-android-x64-3.2.1.tgz", + "integrity": "sha512-HH40xGh3gVQifjOBnhwT2tECC0lL1lYe+nxHvWNSzxDIyQNcVPXg38ta7vuONRFpD+uIrw7fqGYLzbZIagkVcg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@koromix/koffi-darwin-arm64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-darwin-arm64/-/koffi-darwin-arm64-3.2.1.tgz", + "integrity": "sha512-Vj4h+xcjc5+Cn0DhPHjgRX4omKAv96Kehtcd+1YgYuY2W7FvQn9vS+3SmzVwhC5Qmg9bIwUZObYQ8T/4hBqQqA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@koromix/koffi-darwin-x64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-darwin-x64/-/koffi-darwin-x64-3.2.1.tgz", + "integrity": "sha512-gFCWxNBTZIvxo1p+PURWfsy2Ctj5FGnVVs1f03lTLhBvmxEto70pdIiFztdFLDFkAJ1pmtQmruRKapeK+E8YPA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@koromix/koffi-freebsd-arm64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-freebsd-arm64/-/koffi-freebsd-arm64-3.2.1.tgz", + "integrity": "sha512-qj+f1s2e6vULaUG1cdlTcCXmunCq2t+rjxku1+esaMIqVnHpOwj0QzPuInG0AFdXjwBNQhyVR/HpDj8daEwwsQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@koromix/koffi-freebsd-ia32": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-freebsd-ia32/-/koffi-freebsd-ia32-3.2.1.tgz", + "integrity": "sha512-6olHb1Qfgai0jjs6ddlDDD0ZfsCxy7SPi8rMRpuYQWH0qhgtyQu82hw5b1p7z+TJ0zZP3ZeQQ6l+U/MlM1ICHQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@koromix/koffi-freebsd-x64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-freebsd-x64/-/koffi-freebsd-x64-3.2.1.tgz", + "integrity": "sha512-Dikhw1ySYNVMkmeFvFVjnU5Wdk6mffNoOjJxm9bTG96vg7OlemylxqdEven47R1YJ3yzNVJn/MlQ207ORWfi2w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@koromix/koffi-linux-arm": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-arm/-/koffi-linux-arm-3.2.1.tgz", + "integrity": "sha512-OfwUwZylidq95wQKp6ClInULrfB2giu7dqM6Rhe0zAe6lES5I2SXNw15T9+GnRHk3/9hKT2XZ37OZLaKSyWNLA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@koromix/koffi-linux-arm64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-arm64/-/koffi-linux-arm64-3.2.1.tgz", + "integrity": "sha512-K+cGUL5iBcDqxmsocrjmlASqDf24gc7artbVW3PewG2c9AqwC63lezgwvB85Nx4lZAQjB6zIFHh9A7t1yGbwhw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@koromix/koffi-linux-ia32": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-ia32/-/koffi-linux-ia32-3.2.1.tgz", + "integrity": "sha512-rxj6UYjU1qd98gxNQOSCdLpc5cPRi5Giq9rNd3jnGuSNIyMkwa6Dxw4cUjmhIBCYESMJtmNt5NWnJp5u9wTfYQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@koromix/koffi-linux-loong64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-loong64/-/koffi-linux-loong64-3.2.1.tgz", + "integrity": "sha512-aHhnHzkPRmT/IHDlGvESJ/Bs32m8N6UE6Ab6kMeJzgk74IN8af2m/81/wZJtybR1M2UxCV4NmlVNUYQQvSAO3Q==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@koromix/koffi-linux-riscv64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-riscv64/-/koffi-linux-riscv64-3.2.1.tgz", + "integrity": "sha512-qtQBsjbm3LiirLJvajWmKkNb7ARk7fvJVXdftJ7NtAnF3Xw8EbDvrtvmvtNI1yLPlYcBmlzCCD71hwhWYk0SIA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@koromix/koffi-linux-x64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-x64/-/koffi-linux-x64-3.2.1.tgz", + "integrity": "sha512-c7hw7Qs/r5gnFRTQLcbifBwRU7wiocj+2pVuDQ5Ahb3r36SZmupmgYbTWcLvTW+hul1jd7SKRV0d14ZJq/tvSw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@koromix/koffi-openbsd-ia32": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-openbsd-ia32/-/koffi-openbsd-ia32-3.2.1.tgz", + "integrity": "sha512-mmY8fY8LQ/CB52+h3yrMYmVyoxzW3x08S0yI6VNfHWdfU6yJtZkKCbhjmQCYMrWbYKC4gMvwZwCywIGPAkLyeA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@koromix/koffi-openbsd-x64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-openbsd-x64/-/koffi-openbsd-x64-3.2.1.tgz", + "integrity": "sha512-k4ig6aAPbFSRATOIIOfdf/KtlOGH4SVls6L9fy0QnTxRJYvY2oSltTsQtBDANgEQldlq8Kl5WnpRa1VSibP4Lw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@koromix/koffi-win32-arm64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-win32-arm64/-/koffi-win32-arm64-3.2.1.tgz", + "integrity": "sha512-cTWBJGK//pDMeKQJE/79Aq9MiOAF4H8QyLZHSQ9IWm8czOfwjG4J1AhsQ9DjI9KFOykH77hhnpmQTGVMIubGig==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@koromix/koffi-win32-ia32": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-win32-ia32/-/koffi-win32-ia32-3.2.1.tgz", + "integrity": "sha512-Z50EM6TAZ7CFyMmyX6thv8eNpJchqe9eenhibSIy2Eq/FQYF76gU2VK/LEoaF46L8hfC7TpTp9b10MvReHEyFA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@koromix/koffi-win32-x64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-win32-x64/-/koffi-win32-x64-3.2.1.tgz", + "integrity": "sha512-ZmZNiBO6bkOSh3QNzgfvb1cMY0yMobn6ZQrSMqbAce21qyYL8niIbyipz9N/PIRDciGhsV0wUxnZsxIO+yWsHQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@lydell/node-pty": { + "version": "1.2.0-beta.15", + "resolved": "https://registry.npmjs.org/@lydell/node-pty/-/node-pty-1.2.0-beta.15.tgz", + "integrity": "sha512-Br8wBxzbxFwdWgk9uQ+rdzE0xfoxOK4QuGH54swhRwc5IxP6H9Y1/bcyazRGvNUs6XkB5qNVkezuKSRxUwZe7A==", + "license": "MIT", + "optionalDependencies": { + "@lydell/node-pty-darwin-arm64": "1.2.0-beta.15", + "@lydell/node-pty-darwin-x64": "1.2.0-beta.15", + "@lydell/node-pty-linux-arm64": "1.2.0-beta.15", + "@lydell/node-pty-linux-x64": "1.2.0-beta.15", + "@lydell/node-pty-win32-arm64": "1.2.0-beta.15", + "@lydell/node-pty-win32-x64": "1.2.0-beta.15" + } + }, + "node_modules/@lydell/node-pty-darwin-arm64": { + "version": "1.2.0-beta.15", + "resolved": "https://registry.npmjs.org/@lydell/node-pty-darwin-arm64/-/node-pty-darwin-arm64-1.2.0-beta.15.tgz", + "integrity": "sha512-6TSBbzdcLiNTHl1mTuzflqXrkmcC36USVGvERoDgvHk2ItEDaMaFZuAJ1CqPmwYj0DyhCS16TVS8OGK9xZnjyQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lydell/node-pty-darwin-x64": { + "version": "1.2.0-beta.15", + "resolved": "https://registry.npmjs.org/@lydell/node-pty-darwin-x64/-/node-pty-darwin-x64-1.2.0-beta.15.tgz", + "integrity": "sha512-yDT2oqPqYMBScyuk1U9Rg5VKcrbMOD9o9jWYYamDADA3NSbUISroPChrqYRQ74Y7BQtNH4gqYAiWOZRi5uQZ0Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lydell/node-pty-linux-arm64": { + "version": "1.2.0-beta.15", + "resolved": "https://registry.npmjs.org/@lydell/node-pty-linux-arm64/-/node-pty-linux-arm64-1.2.0-beta.15.tgz", + "integrity": "sha512-wkbNF7dYAmtJv+o2+iztVlNwnUB4B0uX0wh/UD+mwMcmE2gNMnW9GChXO7fEE5XJokD0vB5idiHpGegaN+G/sg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lydell/node-pty-linux-x64": { + "version": "1.2.0-beta.15", + "resolved": "https://registry.npmjs.org/@lydell/node-pty-linux-x64/-/node-pty-linux-x64-1.2.0-beta.15.tgz", + "integrity": "sha512-+U/5AVvHT6W+8OCYcnJgN0Qgc0ycO3TfD6aaFJHK+WHij797f8gsi5dV1HEO9l6YQmWCD+VL5gaLDhx3mxHwCA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lydell/node-pty-win32-arm64": { + "version": "1.2.0-beta.15", + "resolved": "https://registry.npmjs.org/@lydell/node-pty-win32-arm64/-/node-pty-win32-arm64-1.2.0-beta.15.tgz", + "integrity": "sha512-pyAk91w7wnnKrD4mrHXtIXRfmzSWV5bEzvRhurXcMCtCc2TJ424ciUskIgWMhAPP6y3KyUnqElj+U6kY3iOt0A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@lydell/node-pty-win32-x64": { + "version": "1.2.0-beta.15", + "resolved": "https://registry.npmjs.org/@lydell/node-pty-win32-x64/-/node-pty-win32-x64-1.2.0-beta.15.tgz", + "integrity": "sha512-2f8twEmDVxZ7drchAXjtevpmSPhFok0avAnzXro4t5gmz0xsPNKkoZvymwtuIS3xo7PzQqZOPQ/YzwEMb7oIzQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@mistralai/mistralai": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.6.4.tgz", + "integrity": "sha512-PPt4GyJqs2hEsWrYCJZK5f0ORmT+L2MSm75LVGD7kBLf6ZKsoDpld/FRBQXr8xG6iFCBOFJFYzvGYhUb+UCkbw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.40.0", + "ws": "^8.18.0", + "zod": "^3.25.0 || ^4.0.0", + "zod-to-json-schema": "^3.25.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.220.0", + "@opentelemetry/resources": "^2.9.0", + "@opentelemetry/sdk-trace-base": "^2.9.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/exporter-trace-otlp-http": { + "optional": true + }, + "@opentelemetry/resources": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", + "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@hono/node-server": "^1.19.9 || ^2.0.5", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@mozilla/readability": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@mozilla/readability/-/readability-0.6.0.tgz", + "integrity": "sha512-juG5VWh4qAivzTAeMzvY9xs9HY5rAcr2E4I7tiSSCokRFi7XIZCAu92ZkSTsIj1OPceCifL3cpfteP3pDT9/QQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@openclaw/ai": { + "version": "2026.9.5", + "resolved": "https://registry.npmjs.org/@openclaw/ai/-/ai-2026.9.5.tgz", + "integrity": "sha512-RBchFFYjhvbN5FM8oBY8N8AGH2k6LTFaHohbQcdnhM7ZvfEjaWRNBmgGw8Q2RqfKjHtEL5sHm7p+EWvFTvfjIw==", + "license": "MIT", + "dependencies": { + "@anthropic-ai/sdk": "0.124.0", + "@google/genai": "2.21.0", + "@mistralai/mistralai": "2.6.4", + "openai": "7.12.1", + "partial-json": "0.1.7", + "typebox": "1.3.30" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@openclaw/fs-safe": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@openclaw/fs-safe/-/fs-safe-0.13.1.tgz", + "integrity": "sha512-nfsEjPirAKotKBaQoQt10JbZx3RwkFIcv2Ujj0m3DCJCP5Jre3AUnMatVinsNL/W60IL4XeRWEXCvlra7edx7g==", + "license": "MIT", + "engines": { + "node": ">=22" + }, + "optionalDependencies": { + "@openclaw/fs-safe-darwin-arm64": "0.13.1", + "@openclaw/fs-safe-darwin-x64": "0.13.1", + "@openclaw/fs-safe-linux-arm64-gnu": "0.13.1", + "@openclaw/fs-safe-linux-arm64-musl": "0.13.1", + "@openclaw/fs-safe-linux-x64-gnu": "0.13.1", + "@openclaw/fs-safe-linux-x64-musl": "0.13.1", + "@openclaw/fs-safe-win32-x64-msvc": "0.13.1", + "jszip": "^3.10.2" + } + }, + "node_modules/@openclaw/fs-safe-darwin-arm64": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@openclaw/fs-safe-darwin-arm64/-/fs-safe-darwin-arm64-0.13.1.tgz", + "integrity": "sha512-dyPCOfMVJMXaP2yJIIyZrvK3TyQpCEduhvvfH3Zbcw/4SrdKP574qghgJM+VCVg2T9TEHTN4YBNO6z52iiMI3g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=22" + } + }, + "node_modules/@openclaw/fs-safe-darwin-x64": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@openclaw/fs-safe-darwin-x64/-/fs-safe-darwin-x64-0.13.1.tgz", + "integrity": "sha512-FJQWzC2ov0anu1sBiK796bKLRQhxvwgi17KPf6Wqt0OAMbC9UVrDuF22Fb4ASPn6CS8+vou64/qurc9XK1OjIA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=22" + } + }, + "node_modules/@openclaw/fs-safe-linux-arm64-gnu": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@openclaw/fs-safe-linux-arm64-gnu/-/fs-safe-linux-arm64-gnu-0.13.1.tgz", + "integrity": "sha512-c/0+bdcYrLz0j/I37la4OcuRteVhqu1usLJqzax7A74dxIAtavARY36hyYIofxxRZPMYayBouB1DzAIpQviXCw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=22" + } + }, + "node_modules/@openclaw/fs-safe-linux-arm64-musl": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@openclaw/fs-safe-linux-arm64-musl/-/fs-safe-linux-arm64-musl-0.13.1.tgz", + "integrity": "sha512-Z5N6wQIJsKFa/GT6nSo2d70TaEydNcWTq3JlYWZxfz1lb2QqbRX7drh8S+doc74dxy+KoVUy+nb1DUYOfX5hEw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=22" + } + }, + "node_modules/@openclaw/fs-safe-linux-x64-gnu": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@openclaw/fs-safe-linux-x64-gnu/-/fs-safe-linux-x64-gnu-0.13.1.tgz", + "integrity": "sha512-necoU8nn6wf04dWk+imTWh+BAT0bwH7MG6tHzl6h/qNNiWFAkcJja85lD+prqKt2e8652HtfGvrSiZfusjzTRg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=22" + } + }, + "node_modules/@openclaw/fs-safe-linux-x64-musl": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@openclaw/fs-safe-linux-x64-musl/-/fs-safe-linux-x64-musl-0.13.1.tgz", + "integrity": "sha512-zrZavePtOGS28uoXwTQas4b91xEAyLoOCplwFhnNfBQ0Tgz9jT2c1bLaD7vNlsWmRYsz9XGcMl4KiXrtruanSg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=22" + } + }, + "node_modules/@openclaw/fs-safe-win32-x64-msvc": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@openclaw/fs-safe-win32-x64-msvc/-/fs-safe-win32-x64-msvc-0.13.1.tgz", + "integrity": "sha512-69r8H9Oz3yMglTQEkE982973QDmnvcrO5L74lkMa29jxQitt/efI7fUtDcs6nHu3+EhVwSF4iemwNtBdpqyuVw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=22" + } + }, + "node_modules/@openclaw/proxyline": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@openclaw/proxyline/-/proxyline-0.3.12.tgz", + "integrity": "sha512-KPUvtLgORF0NWxiuUPwHBuxYhUsFgxnOm8+XkDR0on236lnfMF44A5BnOvBkeDV8wrBaGHs/rQ8kt1tB4WNhmw==", + "license": "MIT", + "engines": { + "node": ">=22.19.0" + }, + "peerDependencies": { + "undici": ">=8.5.0 <9" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", + "license": "BSD-3-Clause" + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, + "node_modules/@silvia-odwyer/photon-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", + "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", + "license": "Apache-2.0" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@trycua/cua-driver": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@trycua/cua-driver/-/cua-driver-0.24.0.tgz", + "integrity": "sha512-OVYuBBvo7HNxGmqZDaWlfQ5tdr7FruA0YfytJ0p+aTZ9QF/1T8G5nQNsLVJht7JBAybktxO6JW+lxBNn9QHp8A==", + "license": "MIT", + "dependencies": { + "@ubjs/core": "0.31.0-3", + "@ubjs/node": "0.31.0-3" + }, + "optionalDependencies": { + "@trycua/cua-driver-darwin-arm64": "0.24.0", + "@trycua/cua-driver-darwin-x64": "0.24.0", + "@trycua/cua-driver-linux-arm64-gnu": "0.24.0", + "@trycua/cua-driver-linux-x64-gnu": "0.24.0", + "@trycua/cua-driver-win32-arm64-msvc": "0.24.0", + "@trycua/cua-driver-win32-x64-msvc": "0.24.0" + } + }, + "node_modules/@trycua/cua-driver-darwin-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@trycua/cua-driver-darwin-arm64/-/cua-driver-darwin-arm64-0.24.0.tgz", + "integrity": "sha512-V6C580ZFIYmB6fxjqM3eEoXahvzGjWc34I1SoVSBjDRjQSn2fXLldlNsCBxEUHs4TH/JopTyP1hTChbNtCC06w==", + "cpu": [ + "arm64" + ], + "license": "MIT AND MPL-2.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@trycua/cua-driver-darwin-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@trycua/cua-driver-darwin-x64/-/cua-driver-darwin-x64-0.24.0.tgz", + "integrity": "sha512-4KmFTUsGegTXmt+Crr/M+Zf/92zI+tGmJWFfM3CkCNGaCBw+wqJFmAYp9mVaR2F+zmdpaWyptB/movaHC88Lcg==", + "cpu": [ + "x64" + ], + "license": "MIT AND MPL-2.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@trycua/cua-driver-linux-arm64-gnu": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@trycua/cua-driver-linux-arm64-gnu/-/cua-driver-linux-arm64-gnu-0.24.0.tgz", + "integrity": "sha512-SWiyMz8z6Oax/Q+Fp2zN4WjxcXE1pSQH9SsET63W2+C6t2CZdhLzypB1h4xNStcX8eCQZ/dl0ayiV2h0U2c/Zw==", + "cpu": [ + "arm64" + ], + "license": "MIT AND MPL-2.0", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@trycua/cua-driver-linux-x64-gnu": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@trycua/cua-driver-linux-x64-gnu/-/cua-driver-linux-x64-gnu-0.24.0.tgz", + "integrity": "sha512-7FDtyemjyaZwNIOahfYPr6NcJOr86i3WXGcaJ9Q+a5wG13srkr3vhA0K7mPQcGd/tZTyZAulXA0fnjYv7oZiXg==", + "cpu": [ + "x64" + ], + "license": "MIT AND MPL-2.0", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@trycua/cua-driver-win32-arm64-msvc": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@trycua/cua-driver-win32-arm64-msvc/-/cua-driver-win32-arm64-msvc-0.24.0.tgz", + "integrity": "sha512-NktveoX98qafCjgBePzqTsHqjlFHdz6ositvEtwVTS3xA7hNEgCsr9w55sRQYuUSGfz5xjxpFioSn6Mh/f5brw==", + "cpu": [ + "arm64" + ], + "license": "MIT AND MPL-2.0", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@trycua/cua-driver-win32-x64-msvc": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@trycua/cua-driver-win32-x64-msvc/-/cua-driver-win32-x64-msvc-0.24.0.tgz", + "integrity": "sha512-8tudsgdnfgCgeavIyhIOVCAAkfKezTkhTaZvPqZnikYN+5xKrO4o+vPhyyuEurYZLBLKluAxRwGr9DP/7syZjw==", + "cpu": [ + "x64" + ], + "license": "MIT AND MPL-2.0", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/node": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.3.tgz", + "integrity": "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg==", + "license": "MIT", + "dependencies": { + "undici-types": "~8.9.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, + "node_modules/@ubjs/core": { + "version": "0.31.0-3", + "resolved": "https://registry.npmjs.org/@ubjs/core/-/core-0.31.0-3.tgz", + "integrity": "sha512-39XrJgUZ2VVb561sSnkXPhczNoeBsNiSRArecsV0JE7CJq69ajFkcn9/tBAUS2NpgHkLIDU+z6Ks2+1wXnboxg==", + "license": "MPL-2.0" + }, + "node_modules/@ubjs/node": { + "version": "0.31.0-3", + "resolved": "https://registry.npmjs.org/@ubjs/node/-/node-0.31.0-3.tgz", + "integrity": "sha512-qNMpi2LICNwxGXZyRF8fSDBSpbezyZbEsydrbiMPJOmtOWr4tmZIEl7jkWGHVGShoBvHfFo4eHp5B4UVP928Cg==", + "license": "MPL-2.0", + "optionalDependencies": { + "@ubjs/node-darwin-arm64": "0.31.0-3", + "@ubjs/node-darwin-x64": "0.31.0-3", + "@ubjs/node-linux-arm64-gnu": "0.31.0-3", + "@ubjs/node-linux-arm64-musl": "0.31.0-3", + "@ubjs/node-linux-x64-gnu": "0.31.0-3", + "@ubjs/node-linux-x64-musl": "0.31.0-3", + "@ubjs/node-win32-arm64-msvc": "0.31.0-3", + "@ubjs/node-win32-x64-msvc": "0.31.0-3" + } + }, + "node_modules/@ubjs/node-darwin-arm64": { + "version": "0.31.0-3", + "resolved": "https://registry.npmjs.org/@ubjs/node-darwin-arm64/-/node-darwin-arm64-0.31.0-3.tgz", + "integrity": "sha512-GGQVPLkVo4Gc8qVLW4IGvS8bjl8eHXyeP4a97ntGmsAdXwE5gsS29o8xUEFROjhwHKD+9sgVeblCSWVG3CpHsw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@ubjs/node-darwin-x64": { + "version": "0.31.0-3", + "resolved": "https://registry.npmjs.org/@ubjs/node-darwin-x64/-/node-darwin-x64-0.31.0-3.tgz", + "integrity": "sha512-2sc47u4XFYOsbmP5EW+Gx8m/yGrYnfFDFQm6+kz7goSWTNg84eEiz3COs9HKJDVuNJ5Khv5XipTO8CFadMLXCw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@ubjs/node-linux-arm64-gnu": { + "version": "0.31.0-3", + "resolved": "https://registry.npmjs.org/@ubjs/node-linux-arm64-gnu/-/node-linux-arm64-gnu-0.31.0-3.tgz", + "integrity": "sha512-YStVXhYz/5jvlWf/p4fhiVT72unYAbGugifFC9QmO/+hnroQDAQ5t8SARbsc15G4olMcamdIB+GETiUB7gmaYg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ubjs/node-linux-arm64-musl": { + "version": "0.31.0-3", + "resolved": "https://registry.npmjs.org/@ubjs/node-linux-arm64-musl/-/node-linux-arm64-musl-0.31.0-3.tgz", + "integrity": "sha512-Izp4nvfy/LmibzFowAztkoDOksCR2fb2zl6fh1ojR1HEsg0rAGruxtI8d3fn8DI0lBXwqmn6SF///oD4mFNJPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ubjs/node-linux-x64-gnu": { + "version": "0.31.0-3", + "resolved": "https://registry.npmjs.org/@ubjs/node-linux-x64-gnu/-/node-linux-x64-gnu-0.31.0-3.tgz", + "integrity": "sha512-Xdm21blyg5U/kW6s7OMvgrr8coGTkUlt26DVR9x8gKISif+E3YwdEskbFScWqystAiJnfjw7xHEc8UMu0Qlz7Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ubjs/node-linux-x64-musl": { + "version": "0.31.0-3", + "resolved": "https://registry.npmjs.org/@ubjs/node-linux-x64-musl/-/node-linux-x64-musl-0.31.0-3.tgz", + "integrity": "sha512-fFQ9BWS6i2LUH9SJgD9oEiKXXo/say59vHy7usFe1t7C2xvwvP34f5SuxXmWP9R8fHyA0aC4kIZ+TTwyHSv1Kw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ubjs/node-win32-arm64-msvc": { + "version": "0.31.0-3", + "resolved": "https://registry.npmjs.org/@ubjs/node-win32-arm64-msvc/-/node-win32-arm64-msvc-0.31.0-3.tgz", + "integrity": "sha512-ID6rSz1NmPsWTNBBNAw4OnJ5Dj8pcbtNJtdPB3OxcGigLBd/e0x7buhSI7os6Mo5iYtEdCynBRQHFJwub5XSPg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@ubjs/node-win32-x64-msvc": { + "version": "0.31.0-3", + "resolved": "https://registry.npmjs.org/@ubjs/node-win32-x64-msvc/-/node-win32-x64-msvc-0.31.0-3.tgz", + "integrity": "sha512-wevs+Y+szwcCUT8IJFbB4/1nfxyRv/51l8oG7FGUPWD1xLPyuHfJBG27C+PDeC7KRzes/2n6aLo4DGZbr/LYTw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bn.js": { + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/boolbase": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-2.0.0.tgz", + "integrity": "sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA==", + "license": "ISC", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-6.0.0.tgz", + "integrity": "sha512-2uNTXIuTTxk7ciZgAU1BQcgnchcG0xXnrs6jzkQfj9SsRa9M2s5zE8WT96hS6KmG4MzWHSrvH43DF1m4XRkrFg==", + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/clawpdf": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/clawpdf/-/clawpdf-0.3.2.tgz", + "integrity": "sha512-1JudW0rZ5B7O2E9+cgnU5F4As7K6HZ3/s/3CHUXm5uG0+965uPOk11gvhmprpwXGJaRXhrmywZ1WWc5a+GZ7TQ==", + "license": "MIT", + "bin": { + "clawpdf": "dist/cli.js" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/croner": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/croner/-/croner-10.0.1.tgz", + "integrity": "sha512-ixNtAJndqh173VQ4KodSdJEI6nuioBWI0V1ITNKhZZsO0pEMoDxz539T4FTTbSZ/xIOSuDnzxLVRqBVSvPNE2g==", + "funding": [ + { + "type": "other", + "url": "https://paypal.me/hexagonpp" + }, + { + "type": "github", + "url": "https://github.com/sponsors/hexagon" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-7.0.0.tgz", + "integrity": "sha512-snmjEVXy+1LnwXdxhYvTMj1d9tOh4HxkA1YmoayVBeeyR2C14Pum7fcxJIm4SswYspVy866eYNwlH6xC3/VH5g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^2.0.0", + "css-what": "^8.0.0", + "domhandler": "^6.0.1", + "domutils": "^4.0.2", + "nth-check": "^3.0.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-8.0.0.tgz", + "integrity": "sha512-DH0Bqq3DNp5tdOReuNyAA+Ev4Y2GS5FMbZpeTLP6C4CDi0h5nL0BmUPChXw3o/qbHLDWHl49sbNqQVY7bMSDdw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/diff": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", + "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-3.1.1.tgz", + "integrity": "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==", + "license": "MIT", + "dependencies": { + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0", + "entities": "^8.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-3.0.0.tgz", + "integrity": "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/domhandler": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-6.0.1.tgz", + "integrity": "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^3.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-4.0.2.tgz", + "integrity": "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^3.0.0", + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.1.0.tgz", + "integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", + "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/execa": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-10.0.1.tgz", + "integrity": "sha512-ge98qjkRK4IB7tL7Ju/6qmm5LHoH1eEMt5FNZrz3f4UIYhF28lggX20z3FaX1sgc67msLEn0N0BscOs29iuwyw==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "figures": "^6.1.0", + "get-stream": "^9.0.1", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.3.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "which-command": "^0.1.0", + "yoctocolors": "^2.1.2" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "peer": true, + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", + "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense" + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-type": { + "version": "22.0.2", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-22.0.2.tgz", + "integrity": "sha512-0H8TsCUGBLx+V5adH3EY52hTAcyLKbV1D4gq5cIOJ6DnQAHeV9Z2Hhuc5CoBX4YmvB2oL+JIC84z0qO7JsCoNw==", + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.5", + "token-types": "^6.1.2", + "uint8array-extras": "^1.5.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gaxios": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.1.tgz", + "integrity": "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/google-auth-library": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.1.tgz", + "integrity": "sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/grammy": { + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/grammy/-/grammy-1.46.0.tgz", + "integrity": "sha512-/8Qw+iisrUdOMk+p2mjEHouMm/BBdBEN1DHh16wiTpRUZkxDG3PxexdjCvR+wvK3LWPdrEvnQbdrwpU954sPhg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@grammyjs/types": "5.0.0", + "abort-controller": "^3.0.0", + "debug": "^4.4.3", + "node-fetch": "^2.7.0" + }, + "engines": { + "node": "^12.20.0 || >=14.13.1" + } + }, + "node_modules/grammy/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/highlight.js": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.12.0.tgz", + "integrity": "sha512-nbfWpyRMcMrPMmDwJB+dhX/eiaPKtc2RB+0QZskqJ3WjRA/FDS0e9hZrx8EC/lbEv8gXy98FcDbNa/dspAaJMg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hono": { + "version": "4.13.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.9.tgz", + "integrity": "sha512-7dMkQmZoC4E6F7AtaQSPhlWAdnBti+j7rreMZl8QB4jFiEhP9TWbGWUMi8WYzBCgmgulxuvLQupKqo+Co6Omyg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/hosted-git-info": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-10.1.1.tgz", + "integrity": "sha512-DeOnSPAvOndYKfw075gt8yZzQ7S2hNztw34zBTfhIzLhmBTswIBg5/y+pqu/VD5cYWm5goAFTusDmUEmKZ0PEQ==", + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/htmlparser2/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http_ece": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http_ece/-/http_ece-1.2.0.tgz", + "integrity": "sha512-JrF8SSLVmcvc5NducxgyOrKXe3EsyHMgBFgSaIUGmArKe+rwr0uphRkRXvwiom3I+fpIfoItveHrfudL8/rxuA==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.9.tgz", + "integrity": "sha512-brTTsvFRt5C1gGHtPst/281UjPD5t9fBqbgoMPlVWy11ZLTPfu7HxK4ZYqO9H7o/yC9rSTCI85EaQ4OoY12qYw==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.2.tgz", + "integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jose": { + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jszip": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.2.tgz", + "integrity": "sha512-3l+rb15IOWtUhU0H5MFqES/T6Kh7abYwjosBey/vD6hDt8zoEffkSC5Ws5SGtgVw3gBx2NEbhTeSW1+kWkpyTQ==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/koffi": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koffi/-/koffi-3.2.1.tgz", + "integrity": "sha512-0qE3lZ8jllRqPN4Ob6Ajl7c2bJSJDhQWuKLGP5hIEpHLllJWv1ydHFMhHmHc5p/W9GticKVDbYzZd7TBoQ4CZg==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "url": "https://liberapay.com/Koromix" + }, + "optionalDependencies": { + "@koromix/koffi-android-arm64": "3.2.1", + "@koromix/koffi-android-x64": "3.2.1", + "@koromix/koffi-darwin-arm64": "3.2.1", + "@koromix/koffi-darwin-x64": "3.2.1", + "@koromix/koffi-freebsd-arm64": "3.2.1", + "@koromix/koffi-freebsd-ia32": "3.2.1", + "@koromix/koffi-freebsd-x64": "3.2.1", + "@koromix/koffi-linux-arm": "3.2.1", + "@koromix/koffi-linux-arm64": "3.2.1", + "@koromix/koffi-linux-ia32": "3.2.1", + "@koromix/koffi-linux-loong64": "3.2.1", + "@koromix/koffi-linux-riscv64": "3.2.1", + "@koromix/koffi-linux-x64": "3.2.1", + "@koromix/koffi-openbsd-ia32": "3.2.1", + "@koromix/koffi-openbsd-x64": "3.2.1", + "@koromix/koffi-win32-arm64": "3.2.1", + "@koromix/koffi-win32-ia32": "3.2.1", + "@koromix/koffi-win32-x64": "3.2.1" + } + }, + "node_modules/kysely": { + "version": "0.29.5", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.29.5.tgz", + "integrity": "sha512-ooa+eSbBNPTo3MycPEuW5jdrxQdQwdtB3LC3h43FiXQbIry5tR0C5lDG7eealK0E4D7XjrnOP5DIUg/LyjRMYQ==", + "license": "MIT", + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/linkedom": { + "version": "0.18.13", + "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.18.13.tgz", + "integrity": "sha512-ES/o9qotMpzpN2MHs+Iq/JcVoOj8Fa5wiQYrTdFpvAnwXL0g66XHHUc9WUMk6nAlBtGsFQ24ne+SYnvnaQ2FSw==", + "license": "ISC", + "dependencies": { + "css-select": "^7.0.0", + "cssom": "^0.5.0", + "html-escaper": "^3.0.3", + "htmlparser2": "^10.1.0", + "uhyphen": "^0.2.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "canvas": ">= 2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/marked": { + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", + "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/node-addon-api": { + "version": "8.9.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.2.tgz", + "integrity": "sha512-VijLXbi3UACN69I0JVXJsX4tjACjNoQDgv2gTF6sx2wWEi8tkSg2eX8p5gSIFi8z2+DL3oHmY6OyKce38SDolg==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-edge-tts": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/node-edge-tts/-/node-edge-tts-1.2.10.tgz", + "integrity": "sha512-bV2i4XU54D45+US0Zm1HcJRkifuB3W438dWyuJEHLQdKxnuqlI1kim2MOvR6Q3XUQZvfF9PoDyR1Rt7aeXhPdQ==", + "license": "MIT", + "dependencies": { + "https-proxy-agent": "^7.0.1", + "ws": "^8.13.0", + "yargs": "^17.7.2" + }, + "bin": { + "node-edge-tts": "bin.js" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nth-check": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-3.0.1.tgz", + "integrity": "sha512-GX0gsdbGVCgnRgbeGaubfjpBXyYRWOOCVeYh08bSQvDZqxz5ndXs1OTfAt/h36G1xvI94YIspsI0sVFqAV9+RQ==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^2.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openai": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/openai/-/openai-7.12.1.tgz", + "integrity": "sha512-D0d8NevCWPe+cxE5kw/ZaBRh1Od4FfhRmD6FlNeLN1j96Xll5MYEJTbaFF9/F3eQHJANjrSuq2nWBCpVT+TmnQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", + "undici": ">=5 <9", + "ws": "^8.21.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "undici": { + "optional": true + }, + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openclaw": { + "version": "2026.9.5", + "resolved": "https://registry.npmjs.org/openclaw/-/openclaw-2026.9.5.tgz", + "integrity": "sha512-TCO/ImVLh5HkF4tdfo7iriIa7kT6iYkIr/jR5ZOkePGFGhUx5Oe7DE716Y1DzzG2teRAVDdCjgJDu1A24Yta7w==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@agentclientprotocol/sdk": "1.4.0", + "@anthropic-ai/sdk": "0.124.0", + "@clack/core": "1.5.0", + "@clack/prompts": "1.8.0", + "@earendil-works/pi-tui": "0.85.1", + "@google/genai": "2.21.0", + "@grammyjs/runner": "2.0.3", + "@grammyjs/transformer-throttler": "1.2.1", + "@homebridge/ciao": "1.3.12", + "@lydell/node-pty": "1.2.0-beta.15", + "@mistralai/mistralai": "2.6.4", + "@modelcontextprotocol/sdk": "1.30.0", + "@mozilla/readability": "0.6.0", + "@openclaw/ai": "2026.9.5", + "@openclaw/fs-safe": "0.13.1", + "@openclaw/proxyline": "0.3.12", + "@silvia-odwyer/photon-node": "0.3.4", + "@trycua/cua-driver": "0.24.0", + "acorn": "8.18.0", + "chalk": "6.0.0", + "chokidar": "5.0.0", + "clawpdf": "0.3.2", + "commander": "15.0.0", + "croner": "10.0.1", + "diff": "9.0.0", + "dotenv": "17.4.2", + "entities": "8.1.0", + "execa": "10.0.1", + "express": "5.2.1", + "file-type": "22.0.2", + "grammy": "1.46.0", + "highlight.js": "11.12.0", + "hosted-git-info": "10.1.1", + "iconv-lite": "0.7.3", + "ignore": "7.0.9", + "import-meta-resolve": "4.2.0", + "jiti": "2.7.0", + "json5": "2.2.3", + "jszip": "3.10.2", + "koffi": "3.2.1", + "kysely": "0.29.5", + "linkedom": "0.18.13", + "minimatch": "10.2.6", + "ms": "2.1.3", + "node-edge-tts": "1.2.10", + "openai": "7.12.1", + "p-limit": "7.3.2", + "p-map": "7.0.7", + "partial-json": "0.1.7", + "playwright-core": "1.63.0", + "pretty-ms": "9.3.1", + "qrcode": "1.5.4", + "quickjs-wasi": "3.6.0", + "rastermill": "0.3.3", + "semver": "7.8.5", + "tar": "7.5.22", + "tree-sitter-bash": "0.25.1", + "tslog": "5.1.0", + "typebox": "1.3.30", + "typescript": "6.0.3", + "undici": "8.10.2", + "web-push": "3.6.7", + "web-tree-sitter": "0.27.0", + "ws": "8.21.3", + "yaml": "2.9.0", + "zod": "4.5.4" + }, + "bin": { + "openclaw": "openclaw.mjs" + }, + "engines": { + "node": ">=24.16.0 <25 || >=26.1.0" + }, + "optionalDependencies": { + "sqlite-vec": "0.1.9" + } + }, + "node_modules/p-limit": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.2.tgz", + "integrity": "sha512-Ll0w3fU24vYpXoZmjjZIee6bJQDgG0oAyo1PdmFYI8UDwJJddaHAypxIH9avUu+t+lSsAwKVsb1jDCMIIChliw==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.2.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.7.tgz", + "integrity": "sha512-VaWRu2i4FJNRtiRWCuuQRgfQ1B7a6+gMSrO+3j0EQi/k0ULfS9kosRxGoiqwzIjZTDI02tGfk5mXXltLg6QtfQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/playwright-core": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/pretty-ms": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.1.tgz", + "integrity": "sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==", + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/protobufjs": { + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", + "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/qrcode/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/qrcode/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/qrcode/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/quickjs-wasi": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/quickjs-wasi/-/quickjs-wasi-3.6.0.tgz", + "integrity": "sha512-/CNzMq42B8ThzUpzjLSF8K50ntVLV3RREyT8zr4wHaP2CZSHFhPkqg3E1GULmTerehf8Bo+UguQ+4LoOMCTC6A==", + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/rastermill": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/rastermill/-/rastermill-0.3.3.tgz", + "integrity": "sha512-tnNH+jymMZxcGfR+w32fbD9xpDytCrHEyYx/OCFTTIf2gAcaiasUSurBA3lObRrRHy1leitG0QRfJB6mV/SJ4w==", + "license": "MIT", + "dependencies": { + "@silvia-odwyer/photon-node": "0.3.4" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sqlite-vec": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/sqlite-vec/-/sqlite-vec-0.1.9.tgz", + "integrity": "sha512-L7XJWRIBNvR9O5+vh1FQ+IGkh/3D2AzVksW5gdtk28m78Hy8skFD0pqReKH1Yp0/BUKRGcffgKvyO/EON5JXpA==", + "license": "MIT OR Apache", + "optional": true, + "optionalDependencies": { + "sqlite-vec-darwin-arm64": "0.1.9", + "sqlite-vec-darwin-x64": "0.1.9", + "sqlite-vec-linux-arm64": "0.1.9", + "sqlite-vec-linux-x64": "0.1.9", + "sqlite-vec-windows-x64": "0.1.9" + } + }, + "node_modules/sqlite-vec-darwin-arm64": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/sqlite-vec-darwin-arm64/-/sqlite-vec-darwin-arm64-0.1.9.tgz", + "integrity": "sha512-jSsZpE42OfBkGL/ItyJTVCUwl6o6Ka3U5rc4j+UBDIQzC1ulSSKMEhQLthsOnF/MdAf1MuAkYhkdKmmcjaIZQg==", + "cpu": [ + "arm64" + ], + "license": "MIT OR Apache", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/sqlite-vec-darwin-x64": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/sqlite-vec-darwin-x64/-/sqlite-vec-darwin-x64-0.1.9.tgz", + "integrity": "sha512-KDlVyqQT7pnOhU1ymB9gs7dMbSoVmKHitT+k1/xkjarcX8bBqPxWrGlK/R+C5WmWkfvWwyq5FfXfiBYCBs6PlA==", + "cpu": [ + "x64" + ], + "license": "MIT OR Apache", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/sqlite-vec-linux-arm64": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/sqlite-vec-linux-arm64/-/sqlite-vec-linux-arm64-0.1.9.tgz", + "integrity": "sha512-5wXVJ9c9kR4CHm/wVqXb/R+XUHTdpZ4nWbPHlS+gc9qQFVHs92Km4bPnCKX4rtcPMzvNis+SIzMJR1SCEwpuUw==", + "cpu": [ + "arm64" + ], + "license": "MIT OR Apache", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/sqlite-vec-linux-x64": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/sqlite-vec-linux-x64/-/sqlite-vec-linux-x64-0.1.9.tgz", + "integrity": "sha512-w3tCH8xK2finW8fQJ/m8uqKodXUZ9KAuAar2UIhz4BHILfpE0WM/MTGCRfa7RjYbrYim5Luk3guvMOGI7T7JQA==", + "cpu": [ + "x64" + ], + "license": "MIT OR Apache", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/sqlite-vec-windows-x64": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/sqlite-vec-windows-x64/-/sqlite-vec-windows-x64-0.1.9.tgz", + "integrity": "sha512-y3gEIyy/17bq2QFPQOWLE68TYWcRZkBQVA2XLrTPHNTOp55xJi/BBBmOm40tVMDMjtP+Elpk6UBUXdaq+46b0Q==", + "cpu": [ + "x64" + ], + "license": "MIT OR Apache", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/standardwebhooks": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.1.1.tgz", + "integrity": "sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ==", + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tree-sitter-bash": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/tree-sitter-bash/-/tree-sitter-bash-0.25.1.tgz", + "integrity": "sha512-7hMytuYIMoXOq24yRulgIxthE9YmggZIOHCyPTTuJcu6EU54tYD+4G39cUb28kxC6jMf/AbPfWGLQtgPTdh3xw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.2.1", + "node-gyp-build": "^4.8.2" + }, + "peerDependencies": { + "tree-sitter": "^0.25.0" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } + } + }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tslog": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/tslog/-/tslog-5.1.0.tgz", + "integrity": "sha512-g77ALovust2HNBX/63ePEJwetIeX0Vi/dlzqBO+ZAg+aERB54KMmaz/khNPxhFpaxKQ6RQj65sK6vIhj1SJgjQ==", + "license": "MIT", + "bin": { + "tslog": "esm/subpaths/cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/fullstack-build/tslog?sponsor=1" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typebox": { + "version": "1.3.30", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.30.tgz", + "integrity": "sha512-vRmBLzlaq9O9dvfGmI5CssLGvDC/R594kH6N/Q1uUU5VPO3PTgQMlWe/UVNdNVTr2EET+FX8BWZkFdYgxTglbQ==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uhyphen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz", + "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==", + "license": "ISC" + }, + "node_modules/uint8array-extras": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.6.0.tgz", + "integrity": "sha512-8iAasVS4wUx0gPLjH8Xtz2PeDzTSiy8QiLGu2DT3X5GU+egFEQhpnbtkehbAwjvDcxIERmCYcysiODFmE3Ud0Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.2.tgz", + "integrity": "sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/undici-types": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/web-push": { + "version": "3.6.7", + "resolved": "https://registry.npmjs.org/web-push/-/web-push-3.6.7.tgz", + "integrity": "sha512-OpiIUe8cuGjrj3mMBFWY+e4MMIkW3SVT+7vEIjvD9kejGUypv8GPDf84JdPWskK8zMRIJ6xYGm+Kxr8YkPyA0A==", + "license": "MPL-2.0", + "dependencies": { + "asn1.js": "^5.3.0", + "http_ece": "1.2.0", + "https-proxy-agent": "^7.0.0", + "jws": "^4.0.0", + "minimist": "^1.2.5" + }, + "bin": { + "web-push": "src/cli.js" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/web-tree-sitter": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.27.0.tgz", + "integrity": "sha512-XK08gj6RwTMQatAG7uVRP8MunqotL/XC19vHgkSPKmELgbGPBj4ECvB8haHOUnyj6ls2B8t42UTro14zxGgAHg==", + "license": "MIT" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-command": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/which-command/-/which-command-0.1.0.tgz", + "integrity": "sha512-XZyoF5/5hZtXitIwzrU4NKK+Wtbb9aB9CezUEw2Q0wlYK8NUYQxC1rRXgNueYLtBAJwXIb+/tFVk4dozciNJMA==", + "license": "MIT", + "bin": { + "which-command": "cli.js" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sindresorhus/which-command?sponsor=1" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", + "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", + "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/deploy/konflux/openclaw/package.json b/deploy/konflux/openclaw/package.json new file mode 100644 index 0000000000..00c138d9fa --- /dev/null +++ b/deploy/konflux/openclaw/package.json @@ -0,0 +1,17 @@ +{ + "name": "agentic-openclaw-prefetch", + "private": true, + "allowScripts": { + "@google/genai": false, + "koffi@3.2.1": true, + "file:///cachi2/output/deps/npm/koffi-3.2.1.tgz": true, + "openclaw@2026.9.5": true, + "file:///cachi2/output/deps/npm/openclaw-2026.9.5.tgz": true, + "protobufjs": false, + "tree-sitter-bash@0.25.1": true, + "file:///cachi2/output/deps/npm/tree-sitter-bash-0.25.1.tgz": true + }, + "dependencies": { + "openclaw": "2026.9.5" + } +} diff --git a/deploy/konflux/openclaw/profile.sh b/deploy/konflux/openclaw/profile.sh new file mode 100755 index 0000000000..363a9a8882 --- /dev/null +++ b/deploy/konflux/openclaw/profile.sh @@ -0,0 +1,17 @@ +# shellcheck shell=sh + +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# OpenClaw defaults for login shells (sandbox exec/connect). Sourced by +# openclaw-start and installed as /etc/profile.d/openclaw.sh. + +case ":${PATH:-}:" in + *:/usr/local/bin:*) ;; + *) PATH="/usr/local/bin${PATH:+:${PATH}}"; export PATH ;; +esac + +: "${OPENCLAW_NO_AUTO_UPDATE:=1}"; export OPENCLAW_NO_AUTO_UPDATE +: "${DO_NOT_TRACK:=1}"; export DO_NOT_TRACK +: "${OPENCLAW_DISABLE_BONJOUR:=1}"; export OPENCLAW_DISABLE_BONJOUR +: "${OPENCLAW_OFFLINE:=1}"; export OPENCLAW_OFFLINE diff --git a/deploy/konflux/openclaw/rpms.in.yaml b/deploy/konflux/openclaw/rpms.in.yaml new file mode 100644 index 0000000000..a17eea9254 --- /dev/null +++ b/deploy/konflux/openclaw/rpms.in.yaml @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# RPM dependencies for Dockerfile.konflux.openclaw +# Runtime (final) stage only; the builder stage installs no RPMs. +# +# The only requested package is crypto-policies-scripts, needed for +# `update-crypto-policies --set DEFAULT:PQ` so Node's system OpenSSL policy +# negotiates post-quantum key exchange, matching the cli, gateway and +# supervisor images. python3 is pulled in as its dependency. +# +# Resolved with `context.bare: true` so the lockfile pins the full dependency +# closure regardless of what the nodejs-24-minimal base already provides. +# +# Target arches and repos are declared in this file (not passed on the command +# line) so Konflux MintMaker can regenerate the lockfile with no CLI flags. +# Regenerate manually with: +# rpm-lockfile-prototype --outfile deploy/konflux/openclaw/rpms.lock.yaml \ +# deploy/konflux/openclaw/rpms.in.yaml +# +# UBI content is public, so repos are inlined here rather than read from the +# base image; rpm-lockfile-prototype resolves versions from the CDN and cannot +# read /etc/yum.repos.d from inside the base image. +contentOrigin: + repos: + - repoid: ubi-9-for-$basearch-baseos-rpms + baseurl: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/$basearch/baseos/os/ + - repoid: ubi-9-for-$basearch-appstream-rpms + baseurl: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/$basearch/appstream/os/ +packages: + - crypto-policies-scripts +arches: + - x86_64 + - aarch64 +# Exclude weak dependencies (Recommends/Supplements) so the closure is minimal +# and deterministic regardless of the regenerating host's DNF default. Must stay +# in sync with the `--setopt=install_weak_deps=0` flag in the Dockerfile. +installWeakDeps: false +context: + bare: true diff --git a/deploy/konflux/openclaw/rpms.lock.yaml b/deploy/konflux/openclaw/rpms.lock.yaml new file mode 100644 index 0000000000..829366f603 --- /dev/null +++ b/deploy/konflux/openclaw/rpms.lock.yaml @@ -0,0 +1,726 @@ +--- +lockfileVersion: 1 +lockfileVendor: redhat +arches: +- arch: aarch64 + packages: + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/a/alternatives-1.24-2.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 42137 + checksum: sha256:6f7c0667ac015bc0d40836c9f55c73ebf65a209069f69aa8f58e6b4655c820a8 + name: alternatives + evr: 1.24-2.el9 + sourcerpm: chkconfig-1.24-2.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/basesystem-11-13.el9.noarch.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 8229 + checksum: sha256:f498b0813fa1a825d550e8e3a9e42255eabfa18e6fc96adfc6cc8fa7e16dd513 + name: basesystem + evr: 11-13.el9 + sourcerpm: basesystem-11-13.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/bash-5.1.8-9.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 1760045 + checksum: sha256:7dc1febec9c2fb184ed4407f8a188ab267b7e46b3534866f702c6266008ababa + name: bash + evr: 5.1.8-9.el9 + sourcerpm: bash-5.1.8-9.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/bzip2-libs-1.0.8-11.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 47655 + checksum: sha256:8267a866b9289ac4e4a92cb4642adcdeff97c2ed816ddda87ed5d5e9d9431a2f + name: bzip2-libs + evr: 1.0.8-11.el9 + sourcerpm: bzip2-1.0.8-11.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/ca-certificates-2025.2.80_v9.0.305-91.el9.noarch.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 1072208 + checksum: sha256:0554bf65d573950e7d550a07bf7d0b3def85ca3b45a7fbde4cce7cadd9a476a7 + name: ca-certificates + evr: 2025.2.80_v9.0.305-91.el9 + sourcerpm: ca-certificates-2025.2.80_v9.0.305-91.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/coreutils-8.32-41.el9_8.1.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 1175876 + checksum: sha256:22827aedd764c1ab706086965859e7f8fef0d719ac0b3d8735f6b8e53b0a13e9 + name: coreutils + evr: 8.32-41.el9_8.1 + sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.1.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 2115646 + checksum: sha256:b449955249a6d2da7369522a5ac2759919d36633e129ba88924057814906d5f5 + name: coreutils-common + evr: 8.32-41.el9_8.1 + sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/crypto-policies-20260224-1.gitea0f072.el9_8.noarch.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 98707 + checksum: sha256:a9ad86b8df75a7c9c29c6f6dd4b8f78cfa1f42b492eff0d11b82c457d37a7f9f + name: crypto-policies + evr: 20260224-1.gitea0f072.el9_8 + sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/crypto-policies-scripts-20260224-1.gitea0f072.el9_8.noarch.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 111065 + checksum: sha256:cec44db94e9132008a7fe4e1aa764a29e9c125989c9ac2411f3cf6f8fb669dfd + name: crypto-policies-scripts + evr: 20260224-1.gitea0f072.el9_8 + sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.5.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 122957 + checksum: sha256:d13bd77f429835b303d388e24811fb935060be4788c8fe4cf87703640f2337e0 + name: expat + evr: 2.5.0-6.el9_8.5 + sourcerpm: expat-2.5.0-6.el9_8.5.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/f/filesystem-3.16-5.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 5003914 + checksum: sha256:484bc41109c49066cf350344150abe144e63263e0fafa0bf12c5a47f853e6a49 + name: filesystem + evr: 3.16-5.el9 + sourcerpm: filesystem-3.16-5.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/f/findutils-4.8.0-7.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 564807 + checksum: sha256:158af4d5ecbd8b87f0da762ea1655bd4c86512071a95d8307eda3e0b3991105d + name: findutils + evr: 1:4.8.0-7.el9 + sourcerpm: findutils-4.8.0-7.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 60311 + checksum: sha256:74fffe15dd7f5a41c7d1990c2804defa1b45fb845da29465b73a81d5866e8a72 + name: gdbm-libs + evr: 1:1.23-1.el9 + sourcerpm: gdbm-1.23-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-2.34-275.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 1813928 + checksum: sha256:e40a78100f731b5f5a1b235880a0aaad5b08bb32e6521e90535b7f4a94c6e9e9 + name: glibc + evr: 2.34-275.el9_8 + sourcerpm: glibc-2.34-275.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-common-2.34-275.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 312665 + checksum: sha256:24e1c7189d101531c526dd3cc1a42ab62d89f874824b54fb6b518ec24f190554 + name: glibc-common + evr: 2.34-275.el9_8 + sourcerpm: glibc-2.34-275.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-minimal-langpack-2.34-275.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 30881 + checksum: sha256:b1348c4c4fe3da979f342b0c27ff0d33a11688274a0b6708292d7750bbc8d853 + name: glibc-minimal-langpack + evr: 2.34-275.el9_8 + sourcerpm: glibc-2.34-275.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gmp-6.2.0-13.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 275679 + checksum: sha256:df01d909e4613514b1844d6ca26d0bcdff8a659762e507188d04ed046fb0cec4 + name: gmp + evr: 1:6.2.0-13.el9 + sourcerpm: gmp-6.2.0-13.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/grep-3.6-5.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 276244 + checksum: sha256:583a247a199901d44dc8a96d46010e15f6211f98f7c61ba089825155b0562520 + name: grep + evr: 3.6-5.el9 + sourcerpm: grep-3.6-5.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libacl-2.4.0-1.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 31468 + checksum: sha256:70ba010505e9805254f772c3dd9cd9e6176fc9e007e8c9be7204c44f85d8bbd2 + name: libacl + evr: 2.4.0-1.el9_8 + sourcerpm: acl-2.4.0-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libattr-2.6.0-1.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 23276 + checksum: sha256:ec08036348dbe2ee41645bdb96eb485b9db5121ec0524258b0639426a22a49bc + name: libattr + evr: 2.6.0-1.el9_8 + sourcerpm: attr-2.6.0-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcap-2.48-10.el9_8.1.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 78021 + checksum: sha256:1ac3014c33b84d7a492b99d46d47940b096e034a3d5886e16ace7159724be012 + name: libcap + evr: 2.48-10.el9_8.1 + sourcerpm: libcap-2.48-10.el9_8.1.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libffi-3.4.2-8.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 38554 + checksum: sha256:d33e180b97a603542cb6f1a78b1c3b0ce4af1bc59ee0bb32620c98a629726bc4 + name: libffi + evr: 3.4.2-8.el9 + sourcerpm: libffi-3.4.2-8.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgcc-11.5.0-14.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 81211 + checksum: sha256:6923218fdef581189a4b51c7ba158083597f1c6df08cae021a1d90e9d61938a9 + name: libgcc + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libselinux-3.6-3.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 89531 + checksum: sha256:3d7249adbf19206e319cd24acc2e01b0da39975aa3e5af73bdb6c6d438108fac + name: libselinux + evr: 3.6-3.el9 + sourcerpm: libselinux-3.6-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsepol-3.6-3.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 326966 + checksum: sha256:496ed9e2d7fac9704afe764eab4c2c43b4a47e8c229c14498dd19786f98f80c0 + name: libsepol + evr: 3.6-3.el9 + sourcerpm: libsepol-3.6-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsigsegv-2.13-4.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 30566 + checksum: sha256:0998ac158161c9d5f3b97c5dc6e35becd84da0ddc5d347a8af581ada529b3b5c + name: libsigsegv + evr: 2.13-4.el9 + sourcerpm: libsigsegv-2.13-4.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libtasn1-4.16.0-10.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 80446 + checksum: sha256:322524934c9b1f0714d2299705dbd41ec93451078e8144babc88c51bd19f6e07 + name: libtasn1 + evr: 4.16.0-10.el9_8 + sourcerpm: libtasn1-4.16.0-10.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libuuid-2.37.4-25.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 32555 + checksum: sha256:4d7bb4144053a30067a82423fb6e88fd08c7a69bd256eb21b08c0e3f4dbbaee6 + name: libuuid + evr: 2.37.4-25.el9 + sourcerpm: util-linux-2.37.4-25.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libxcrypt-4.4.18-3.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 127655 + checksum: sha256:f05030123425a5033bcca3f260313cafc199bc7bca57e9fb13c335bd087c35a7 + name: libxcrypt + evr: 4.4.18-3.el9 + sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/n/ncurses-base-6.2-12.20210508.el9.noarch.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 97840 + checksum: sha256:d62dfd41f9688efa2cf1ceedb96084c63e297fbdcfd1e72bc6757c730092b60c + name: ncurses-base + evr: 6.2-12.20210508.el9 + sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/n/ncurses-libs-6.2-12.20210508.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 324624 + checksum: sha256:b5dd452392d2f97bb050c9f5e5376998652c567dcbd8f035d26659b1b551b5c9 + name: ncurses-libs + evr: 6.2-12.20210508.el9 + sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 14220 + checksum: sha256:158193d2f965db318148ec76e9347b530ac1f5379d849012c0a04d3c50cda478 + name: openssl-fips-provider + evr: 3.0.7-11.el9_8 + sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-fips-provider-so-3.0.7-11.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 529340 + checksum: sha256:22374a51f8a529dcfcf3b3ebbb2095103e0e811a28953535e5a62e26d1233301 + name: openssl-fips-provider-so + evr: 3.0.7-11.el9_8 + sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-libs-3.5.8-1.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 2294194 + checksum: sha256:23134af6ea097b94d8261367db01f91604ef3b508654caf4f7399e5c142abcc8 + name: openssl-libs + evr: 1:3.5.8-1.el9_8 + sourcerpm: openssl-3.5.8-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/p11-kit-0.26.4-1.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 589112 + checksum: sha256:b9391ea6618098782c9325ccda3c9ca8bc0f3b035bd4f113a793cea18026c75e + name: p11-kit + evr: 0.26.4-1.el9_8 + sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/p11-kit-trust-0.26.4-1.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 162392 + checksum: sha256:a57761123cd5836faf3d40251d16124557ffb452443bfa14cf59ac16e6c99970 + name: p11-kit-trust + evr: 0.26.4-1.el9_8 + sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pcre-8.44-4.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 187289 + checksum: sha256:099feef7e71b82cf0234e37d824fc81353d51dee55694e05181fa686ab50efae + name: pcre + evr: 8.44-4.el9 + sourcerpm: pcre-8.44-4.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pcre2-10.40-6.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 224938 + checksum: sha256:29285f81cef68f73b4f8ff81ee8fdf4ceaa007933302119ed1615e4aa1091613 + name: pcre2 + evr: 10.40-6.el9 + sourcerpm: pcre2-10.40-6.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pcre2-syntax-10.40-6.el9.noarch.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 147926 + checksum: sha256:d386b5e9b3a4b077b2ba143882e605750855dd3354f13c55fa12ed26908cb442 + name: pcre2-syntax + evr: 10.40-6.el9 + sourcerpm: pcre2-10.40-6.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-3.9.25-7.el9_8.3.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 33143 + checksum: sha256:67e6d2eca7f6558030dd215a4d2d3ede810231faad0f317eb2bcbc7e9ac619ce + name: python3 + evr: 3.9.25-7.el9_8.3 + sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-libs-3.9.25-7.el9_8.3.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 8470487 + checksum: sha256:014bbdef3d3d00d09c34df9aaf377337e338e31c4106da6c98b844339fcd50d6 + name: python3-libs + evr: 3.9.25-7.el9_8.3 + sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-pip-wheel-21.3.1-2.el9_8.noarch.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 1198443 + checksum: sha256:918041963ad5c3b91276bf1ed3307280673ca8352e2e632bbb3847b33d2bc15a + name: python3-pip-wheel + evr: 21.3.1-2.el9_8 + sourcerpm: python-pip-21.3.1-2.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-setuptools-wheel-53.0.0-15.el9.noarch.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 479203 + checksum: sha256:36dacb345e21bc0308ef2508f0c93995520a15ef0b56aab3593186c8dc9c0c5a + name: python3-setuptools-wheel + evr: 53.0.0-15.el9 + sourcerpm: python-setuptools-53.0.0-15.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/r/readline-8.1-4.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 219015 + checksum: sha256:2ae424b368c6747124b51b205b9e11d74aeaff56b3de90e8cbd36012e0d17707 + name: readline + evr: 8.1-4.el9 + sourcerpm: readline-8.1-4.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/r/redhat-release-9.8-1.0.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 61683 + checksum: sha256:fa7f1d93927c7f8c6f6563a8d221af659074f026e4b12cd74d456b0db1878164 + name: redhat-release + evr: 9.8-1.0.el9 + sourcerpm: redhat-release-9.8-1.0.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/sed-4.8-10.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 315893 + checksum: sha256:b73d314a8ef322a690bb69c49cb0dbd9a5ff18d2ba6b2973e18d2c076a52b62a + name: sed + evr: 4.8-10.el9 + sourcerpm: sed-4.8-10.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/setup-2.13.7-10.el9.noarch.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 153791 + checksum: sha256:0891d395ce067121c28932534237ad1ce231f2bfa987411ad62e73a12d11eb6a + name: setup + evr: 2.13.7-10.el9 + sourcerpm: setup-2.13.7-10.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/sqlite-libs-3.34.1-11.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 660770 + checksum: sha256:0fbb8043f9c02870c831da433f69b856a6674d02b60306d9cc01149ec89ed239 + name: sqlite-libs + evr: 3.34.1-11.el9_8 + sourcerpm: sqlite-3.34.1-11.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 933286 + checksum: sha256:8d6196e02853c0900c3c2e2367665beffb62cb2d0ff5f465ea2d00848a9a35dc + name: tzdata + evr: 2026c-1.el9_8 + sourcerpm: tzdata-2026c-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/x/xz-libs-5.2.5-8.el9_0.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 94569 + checksum: sha256:06931afb372ed4a6893e51558beaa6b0eab7adda0af93456fd99a081a8b80779 + name: xz-libs + evr: 5.2.5-8.el9_0 + sourcerpm: xz-5.2.5-8.el9_0.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/z/zlib-1.2.11-40.el9.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 94454 + checksum: sha256:2e7f193e67235130c10f5579c2d2ec92e22e4098b6d12fb2855d93b1540c60f7 + name: zlib + evr: 1.2.11-40.el9 + sourcerpm: zlib-1.2.11-40.el9.src.rpm + source: [] + module_metadata: [] +- arch: x86_64 + packages: + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/a/alternatives-1.24-2.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 42874 + checksum: sha256:1c520b9bf7b592d936bb347a5107702e51678e160b88ecfbba6a30e35e47d24e + name: alternatives + evr: 1.24-2.el9 + sourcerpm: chkconfig-1.24-2.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/basesystem-11-13.el9.noarch.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 8229 + checksum: sha256:f498b0813fa1a825d550e8e3a9e42255eabfa18e6fc96adfc6cc8fa7e16dd513 + name: basesystem + evr: 11-13.el9 + sourcerpm: basesystem-11-13.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/bash-5.1.8-9.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 1769540 + checksum: sha256:d3adf8b09aa0bf935c67aa12444e0ee02f70a82c2682bfb2b02bda0a989bb806 + name: bash + evr: 5.1.8-9.el9 + sourcerpm: bash-5.1.8-9.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/bzip2-libs-1.0.8-11.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 46333 + checksum: sha256:948f763ed17672b8dd83356541e27a53ce97c6df38339c4416a188d452ca4d1e + name: bzip2-libs + evr: 1.0.8-11.el9 + sourcerpm: bzip2-1.0.8-11.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/ca-certificates-2025.2.80_v9.0.305-91.el9.noarch.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 1072208 + checksum: sha256:0554bf65d573950e7d550a07bf7d0b3def85ca3b45a7fbde4cce7cadd9a476a7 + name: ca-certificates + evr: 2025.2.80_v9.0.305-91.el9 + sourcerpm: ca-certificates-2025.2.80_v9.0.305-91.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/coreutils-8.32-41.el9_8.1.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 1222083 + checksum: sha256:374257c4cd69107333a7f524dd99579f3ea3ab842b66168eade0a3475fb6eea1 + name: coreutils + evr: 8.32-41.el9_8.1 + sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.1.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 2113503 + checksum: sha256:41f69eb8b2087feaa98d0228fb933b6fe8af20a4bf371cfef51e11cbd1f84b4e + name: coreutils-common + evr: 8.32-41.el9_8.1 + sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/crypto-policies-20260224-1.gitea0f072.el9_8.noarch.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 98707 + checksum: sha256:a9ad86b8df75a7c9c29c6f6dd4b8f78cfa1f42b492eff0d11b82c457d37a7f9f + name: crypto-policies + evr: 20260224-1.gitea0f072.el9_8 + sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/crypto-policies-scripts-20260224-1.gitea0f072.el9_8.noarch.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 111065 + checksum: sha256:cec44db94e9132008a7fe4e1aa764a29e9c125989c9ac2411f3cf6f8fb669dfd + name: crypto-policies-scripts + evr: 20260224-1.gitea0f072.el9_8 + sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.5.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 129088 + checksum: sha256:e6e7edd632fdd1dc4ad94b19b7ab88646f3ef8c1c7956bac58985c3e7118a362 + name: expat + evr: 2.5.0-6.el9_8.5 + sourcerpm: expat-2.5.0-6.el9_8.5.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/f/filesystem-3.16-5.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 5003807 + checksum: sha256:9567592e6e32a9ebd45584cc4feb5d00812f143fcb2d8cd8b1d95108f4f66a2d + name: filesystem + evr: 3.16-5.el9 + sourcerpm: filesystem-3.16-5.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/f/findutils-4.8.0-7.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 563531 + checksum: sha256:a6328afea0a11647b7fb5c48436f0af6c795407bac0650676d3196dd47070de6 + name: findutils + evr: 1:4.8.0-7.el9 + sourcerpm: findutils-4.8.0-7.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 60152 + checksum: sha256:c8b8346a98d921206666ce740a3647a52ad7a87c2d01d73166165b3e9a789a6c + name: gdbm-libs + evr: 1:1.23-1.el9 + sourcerpm: gdbm-1.23-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-2.34-275.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 2083064 + checksum: sha256:7d2d420b97c05c09ee1e9bd881cb0725fe8d89688b933f411f278cb23210c65d + name: glibc + evr: 2.34-275.el9_8 + sourcerpm: glibc-2.34-275.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-common-2.34-275.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 321585 + checksum: sha256:f23581b888783f576bd3a55503618a48f74f468fcfd4837bbd7a2d00fe7f3530 + name: glibc-common + evr: 2.34-275.el9_8 + sourcerpm: glibc-2.34-275.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-minimal-langpack-2.34-275.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 30913 + checksum: sha256:6cc48d78bf2ceacfa5b58633b648c564b66505f4677adea8eb663fe90acd76f2 + name: glibc-minimal-langpack + evr: 2.34-275.el9_8 + sourcerpm: glibc-2.34-275.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gmp-6.2.0-13.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 326840 + checksum: sha256:d4529445e30b7eb9a8225b0539f70d26d585d7fe306296f948ea73114d1c171f + name: gmp + evr: 1:6.2.0-13.el9 + sourcerpm: gmp-6.2.0-13.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/grep-3.6-5.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 279174 + checksum: sha256:5556895ff1817066ca71b50785615e944b0fcc7e1c94c983087c7c691819623d + name: grep + evr: 3.6-5.el9 + sourcerpm: grep-3.6-5.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libacl-2.4.0-1.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 31657 + checksum: sha256:a81fb7a4d7c946e9bd886ee3c471a4b5040dedbb8ffb2a388120b2094be93cc8 + name: libacl + evr: 2.4.0-1.el9_8 + sourcerpm: acl-2.4.0-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libattr-2.6.0-1.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 23752 + checksum: sha256:9e37537f690c748f7f05faa80966072f118ec722e38ef332fe19cf22b087349f + name: libattr + evr: 2.6.0-1.el9_8 + sourcerpm: attr-2.6.0-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcap-2.48-10.el9_8.1.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 78928 + checksum: sha256:d4805439b10fa551b7535cf30ca28d4d5862132c9c429b2b31221bea7f43263a + name: libcap + evr: 2.48-10.el9_8.1 + sourcerpm: libcap-2.48-10.el9_8.1.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libffi-3.4.2-8.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 40619 + checksum: sha256:dde0012a94c6f3825e605b095b15767d89c2b87a5da097348310d7e87721c645 + name: libffi + evr: 3.4.2-8.el9 + sourcerpm: libffi-3.4.2-8.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgcc-11.5.0-14.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 87280 + checksum: sha256:77c66827ffc14df2f43612b26128b1fd58d5c9597d4d4e564aa239b161272872 + name: libgcc + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libselinux-3.6-3.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 89722 + checksum: sha256:ce1cc63a7212c39f5f2a35f719ee38d6418cf081ea78c9317f388d9f41e4a627 + name: libselinux + evr: 3.6-3.el9 + sourcerpm: libselinux-3.6-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsepol-3.6-3.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 338766 + checksum: sha256:b98984b2bf42203964cc979ac157df090c63b89a0f5c6560ede01965531c8ffd + name: libsepol + evr: 3.6-3.el9 + sourcerpm: libsepol-3.6-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsigsegv-2.13-4.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 30681 + checksum: sha256:24005c62017797b612d047a2af83a218633b32302a787fabd22e52230db6adc1 + name: libsigsegv + evr: 2.13-4.el9 + sourcerpm: libsigsegv-2.13-4.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libtasn1-4.16.0-10.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 81418 + checksum: sha256:f9473f322407f10205b0db98b89cf8f603e9c769e9977250734136df56cbb981 + name: libtasn1 + evr: 4.16.0-10.el9_8 + sourcerpm: libtasn1-4.16.0-10.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libuuid-2.37.4-25.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 32757 + checksum: sha256:5694aafca42c707f85af66bba11d102f7636ee17f586466b3ff80254e995ed7b + name: libuuid + evr: 2.37.4-25.el9 + sourcerpm: util-linux-2.37.4-25.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libxcrypt-4.4.18-3.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 122599 + checksum: sha256:a50bb26a28ee7e6379c86b5b91285299b71569fa87ea968d800a56090b7a179d + name: libxcrypt + evr: 4.4.18-3.el9 + sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/n/ncurses-base-6.2-12.20210508.el9.noarch.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 97840 + checksum: sha256:d62dfd41f9688efa2cf1ceedb96084c63e297fbdcfd1e72bc6757c730092b60c + name: ncurses-base + evr: 6.2-12.20210508.el9 + sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/n/ncurses-libs-6.2-12.20210508.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 336270 + checksum: sha256:f3e1f8e59c7116278aa19b6705a1443f6307d4d6fbdde75a23d2f5d60636cb16 + name: ncurses-libs + evr: 6.2-12.20210508.el9 + sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 14256 + checksum: sha256:c00860e9c5a1d90488aa2eb65fe41f62926b38c6b3669d331a8b97e4a60223ac + name: openssl-fips-provider + evr: 3.0.7-11.el9_8 + sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-fips-provider-so-3.0.7-11.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 595008 + checksum: sha256:60d36ad3a67d6b00e67bb0a19c0902fbb2ecdd873cf7f280a3039d04a092790c + name: openssl-fips-provider-so + evr: 3.0.7-11.el9_8 + sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-libs-3.5.8-1.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 2430624 + checksum: sha256:3abe1f190415d91d4dc51db74cf2ad7e06b1e5ca5f63dac978fe58d8fd14e493 + name: openssl-libs + evr: 1:3.5.8-1.el9_8 + sourcerpm: openssl-3.5.8-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/p11-kit-0.26.4-1.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 625862 + checksum: sha256:a00ba14bfd0fc5dd2818f605f2e0520b52ea4b36167504c2523f92a065c2bdaf + name: p11-kit + evr: 0.26.4-1.el9_8 + sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/p11-kit-trust-0.26.4-1.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 165521 + checksum: sha256:41b84ab0ee4cf914d570a3d648ed98b7de44cef73ea3dfa58ff5eebdec85f42a + name: p11-kit-trust + evr: 0.26.4-1.el9_8 + sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pcre-8.44-4.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 205261 + checksum: sha256:e9ddc7d57d4f6e7400b66bcc78b9bafc1f05630e3e0d2a14000bc907f429ddc4 + name: pcre + evr: 8.44-4.el9 + sourcerpm: pcre-8.44-4.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pcre2-10.40-6.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 241900 + checksum: sha256:75db1e5a50e7b1794d7ba18212d95cd2684559da9e7c52eee46490302c7f24dd + name: pcre2 + evr: 10.40-6.el9 + sourcerpm: pcre2-10.40-6.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pcre2-syntax-10.40-6.el9.noarch.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 147926 + checksum: sha256:d386b5e9b3a4b077b2ba143882e605750855dd3354f13c55fa12ed26908cb442 + name: pcre2-syntax + evr: 10.40-6.el9 + sourcerpm: pcre2-10.40-6.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-3.9.25-7.el9_8.3.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 33200 + checksum: sha256:f0d622eb17a038e98c23ef9bd6961c104c55b3534f69c301b2067cd9161f40f7 + name: python3 + evr: 3.9.25-7.el9_8.3 + sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-libs-3.9.25-7.el9_8.3.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 8483866 + checksum: sha256:6227afce7123d2e6c46a4a6d318087c512caebf6e09f7bfc0ba73f7e5853fba4 + name: python3-libs + evr: 3.9.25-7.el9_8.3 + sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-pip-wheel-21.3.1-2.el9_8.noarch.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 1198443 + checksum: sha256:918041963ad5c3b91276bf1ed3307280673ca8352e2e632bbb3847b33d2bc15a + name: python3-pip-wheel + evr: 21.3.1-2.el9_8 + sourcerpm: python-pip-21.3.1-2.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-setuptools-wheel-53.0.0-15.el9.noarch.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 479203 + checksum: sha256:36dacb345e21bc0308ef2508f0c93995520a15ef0b56aab3593186c8dc9c0c5a + name: python3-setuptools-wheel + evr: 53.0.0-15.el9 + sourcerpm: python-setuptools-53.0.0-15.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/r/readline-8.1-4.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 220174 + checksum: sha256:01bf315b3bc44c28515c4d33d49173b23d7979d2a09b7b15f749d434b60851e6 + name: readline + evr: 8.1-4.el9 + sourcerpm: readline-8.1-4.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/r/redhat-release-9.8-1.0.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 61742 + checksum: sha256:8157ed988fc34dcfeb6429272959471edd5bde4ac212f26611fd54c180391758 + name: redhat-release + evr: 9.8-1.0.el9 + sourcerpm: redhat-release-9.8-1.0.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/sed-4.8-10.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 317456 + checksum: sha256:45e246453dc9eb1bad6a71c6f349aad1b1b2e1bf3ec645b80f0ed04fe69c960e + name: sed + evr: 4.8-10.el9 + sourcerpm: sed-4.8-10.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/setup-2.13.7-10.el9.noarch.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 153791 + checksum: sha256:0891d395ce067121c28932534237ad1ce231f2bfa987411ad62e73a12d11eb6a + name: setup + evr: 2.13.7-10.el9 + sourcerpm: setup-2.13.7-10.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/sqlite-libs-3.34.1-11.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 665095 + checksum: sha256:e5c20e933ec01f746a6c59a94cb99f46c6138dab3f387f0d02dab47f356e2e98 + name: sqlite-libs + evr: 3.34.1-11.el9_8 + sourcerpm: sqlite-3.34.1-11.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 933286 + checksum: sha256:8d6196e02853c0900c3c2e2367665beffb62cb2d0ff5f465ea2d00848a9a35dc + name: tzdata + evr: 2026c-1.el9_8 + sourcerpm: tzdata-2026c-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/x/xz-libs-5.2.5-8.el9_0.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 96649 + checksum: sha256:de263f880a4394f04b5e84254ba0a88d781b5bd63665c9e028bc10351490c982 + name: xz-libs + evr: 5.2.5-8.el9_0 + sourcerpm: xz-5.2.5-8.el9_0.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/z/zlib-1.2.11-40.el9.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 95708 + checksum: sha256:baf95ffbf40ee014135f16fe33e343faf7ff1ca06509fd97cd988e6afeabf670 + name: zlib + evr: 1.2.11-40.el9 + sourcerpm: zlib-1.2.11-40.el9.src.rpm + source: [] + module_metadata: [] diff --git a/deploy/konflux/openclaw/smoke-test.sh b/deploy/konflux/openclaw/smoke-test.sh new file mode 100755 index 0000000000..b788a06094 --- /dev/null +++ b/deploy/konflux/openclaw/smoke-test.sh @@ -0,0 +1,318 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +# Contract smoke tests for a built odh-openshell-openclaw image. Called by +# build-local.sh after a build; also runnable by hand. +# +# Usage: smoke-test.sh +# +# PLATFORM defaults from the host's `uname -m` (linux/amd64 or linux/arm64) +# and also accepts linux/x86_64 and linux/aarch64; +# every `podman run` below is pinned to it with --platform, --rm and +# --network none. Requires host `openssl` for the DEFAULT:PQ check's +# throwaway certificate. +# +# `openclaw-start --version` only proves onboarding plus the launcher fast +# path (openclaw.mjs returns before loading config). Agent turns, `tui`, +# `doctor` and `config get/set/validate` are deliberately not run here: EDR +# on maintainers' laptops kills OpenClaw's sqlite workers. Some EDR policies +# also kill any OpenClaw process (exit 137), which fails the version and +# contract-b/c/d checks; run this on a host without such a policy. + +if [[ $# -ne 1 ]]; then + echo "Usage: smoke-test.sh " >&2 + exit 2 +fi + +image="$1" + +case "$(uname -m)" in + x86_64) default_platform="linux/amd64" ;; + aarch64) default_platform="linux/arm64" ;; + *) default_platform="linux/$(uname -m)" ;; +esac +PLATFORM="${PLATFORM:-${default_platform}}" +# podman reports OCI arch names; accept the uname aliases build-local.sh takes. +case "${PLATFORM##*/}" in + x86_64|amd64) arch="amd64" ;; + aarch64|arm64) arch="arm64" ;; + *) arch="${PLATFORM##*/}" ;; +esac + +workdir=$(mktemp -d) +# mktemp -d defaults to 0700; the containers below run as a UID that only +# maps to an unprivileged subordinate UID on the host (rootless podman), so +# bind-mounted paths under here need to stay traversable by non-owners. +chmod 0755 "${workdir}" +cleanup() { + rm -rf "${workdir}" +} +trap cleanup EXIT + +failures=0 + +pass() { + echo "PASS $1" +} + +fail() { + echo "FAIL $1: $2" + failures=$((failures + 1)) +} + +podman_run() { + podman run --rm --platform "${PLATFORM}" --network none "$@" +} + +label() { + podman image inspect "${image}" --format "{{index .Config.Labels \"$1\"}}" +} + +# --- arch ------------------------------------------------------------- +got_arch=$(podman image inspect "${image}" --format '{{.Architecture}}') +if [[ "${got_arch}" == "${arch}" ]]; then + pass "arch" +else + fail "arch" "expected ${arch}, got ${got_arch}" +fi + +# --- config ------------------------------------------------------------- +got_user=$(podman image inspect "${image}" --format '{{.Config.User}}') +got_entrypoint=$(podman image inspect "${image}" --format '{{.Config.Entrypoint}}') +got_cmd=$(podman image inspect "${image}" --format '{{.Config.Cmd}}') +got_workdir=$(podman image inspect "${image}" --format '{{.Config.WorkingDir}}') +if [[ "${got_user}" == "1000:1000" \ + && ( -z "${got_entrypoint}" || "${got_entrypoint}" == "[]" ) \ + && "${got_cmd}" == "[/usr/local/bin/openclaw-start]" \ + && ( "${got_workdir}" == "/" || -z "${got_workdir}" ) ]]; then + pass "config" +else + fail "config" "user=${got_user} entrypoint=${got_entrypoint} cmd=${got_cmd} workdir=${got_workdir}" +fi + +# --- labels ------------------------------------------------------------- +got_name=$(label name) +got_component=$(label com.redhat.component) +got_harness=$(label io.openshell.sandbox.harness) +got_harness_version=$(label io.openshell.harness.version) +got_s2i1=$(label io.openshift.s2i.scripts-url) +got_s2i2=$(label io.s2i.scripts-url) +got_license=$(label com.redhat.license_terms) +if [[ "${got_name}" == "opendatahub/odh-openshell-openclaw" \ + && "${got_component}" == "odh-openshell-openclaw-container" \ + && "${got_harness}" == "openclaw" \ + && -n "${got_harness_version}" \ + && -z "${got_s2i1}" \ + && -z "${got_s2i2}" \ + && "${got_license}" == *"#UBI"* ]]; then + pass "labels" +else + fail "labels" "name=${got_name} component=${got_component} harness=${got_harness} harness_version=${got_harness_version} s2i1=${got_s2i1} s2i2=${got_s2i2} license=${got_license}" +fi + +# --- version -------------------------------------------------------------- +if version_out=$(podman_run "${image}" openclaw --version 2>&1); then + version_rc=0 +else + version_rc=$? +fi +if [[ ${version_rc} -eq 0 && "${version_out}" == "OpenClaw ${got_harness_version} "* ]]; then + pass "version" +else + fail "version" "rc=${version_rc} out='${version_out}'" +fi + +# --- layout ----------------------------------------------------------- +# shellcheck disable=SC2016 # runs inside the container's shell, not the host's +if layout_out=$(podman_run "${image}" bash -c ' +set -euo pipefail +if [[ -e /sandbox ]]; then echo "SANDBOX_EXISTS"; exit 1; fi +perm=$(stat -c %a /tmp) +if [[ "$perm" != "1777" ]]; then echo "TMP_PERM=$perm"; exit 1; fi +bad=$(find /usr/local/lib/openclaw \! -perm -o=r -print -quit) +if [[ -n "$bad" ]]; then echo "UNREADABLE=$bad"; exit 1; fi +bad_dir=$(find /usr/local/lib/openclaw -type d \! -perm -o=x -print -quit) +if [[ -n "$bad_dir" ]]; then echo "UNSEARCHABLE=$bad_dir"; exit 1; fi +echo OK +' 2>&1); then + layout_rc=0 +else + layout_rc=$? +fi +if [[ ${layout_rc} -eq 0 && "${layout_out}" == "OK" ]]; then + pass "layout" +else + fail "layout" "rc=${layout_rc} out='${layout_out}'" +fi + +# --- pq (DEFAULT:PQ crypto policy honored by Node's system OpenSSL) ------- +if config_out=$(podman_run "${image}" cat /etc/crypto-policies/config 2>&1); then + config_ok=$([[ "${config_out}" == "DEFAULT:PQ" ]] && echo 1 || echo 0) +else + config_ok=0 +fi + +openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -days 1 \ + -subj /CN=localhost \ + -keyout "${workdir}/key.pem" -out "${workdir}/cert.pem" >/dev/null 2>&1 +chmod 0644 "${workdir}/key.pem" "${workdir}/cert.pem" + +cat > "${workdir}/pq-check.mjs" <<'NODEEOF' +import tls from 'node:tls'; +import fs from 'node:fs'; + +const cert = fs.readFileSync('/t/cert.pem'); +const key = fs.readFileSync('/t/key.pem'); + +const server = tls.createServer({ cert, key, minVersion: 'TLSv1.3', maxVersion: 'TLSv1.3' }, (socket) => { + socket.end(); +}); + +server.on('error', (err) => { + console.error('SERVER_ERROR', err.message); + process.exit(1); +}); + +server.listen(0, '127.0.0.1', () => { + const { port } = server.address(); + const client = tls.connect( + { host: '127.0.0.1', port, rejectUnauthorized: false, minVersion: 'TLSv1.3', maxVersion: 'TLSv1.3' }, + () => { + console.log(client.getEphemeralKeyInfo().name); + client.end(); + server.close(); + }, + ); + client.on('error', (err) => { + console.error('CLIENT_ERROR', err.message); + process.exit(1); + }); +}); +NODEEOF +chmod 0644 "${workdir}/pq-check.mjs" + +if pq_out=$(podman_run --user 1000:1000 -v "${workdir}:/t:ro,Z" "${image}" node /t/pq-check.mjs 2>&1); then + pq_group=$(printf '%s\n' "${pq_out}" | tail -n1) +else + pq_group="" +fi +if [[ ${config_ok} -eq 1 && "${pq_group}" == "X25519MLKEM768" ]]; then + pass "pq" +else + fail "pq" "config='${config_out}' node_out='${pq_out}'" +fi + +# --- contract (uid 51234, no passwd entry, read-only rootfs) -------------- +# One container, so the onboarded config persists in a single /sandbox tmpfs +# across the sub-checks. The inner script never uses `set -e`: it prints a +# PASS/FAIL line per sub-check, and the loop below collects them. +if contract_out=$(podman_run -i --user 51234:51234 --passwd=false --read-only \ + --tmpfs /tmp:rw,mode=1777 --tmpfs /sandbox:rw,mode=1777 \ + -e HOME=/sandbox "${image}" bash -s 2>&1 <<'CONTRACT' +base_url="http://model.example.svc.cluster.local:8000/v1" +cfg=/sandbox/.openclaw/openclaw.json +oneline() { tr '\n' ' ' | cut -c1-400; } + +# (a) no MODEL_* env: exit 2 naming MODEL_BASE_URL +err=$(openclaw-start 2>&1 >/dev/null); rc=$? +if [[ $rc -eq 2 && "$err" == *MODEL_BASE_URL* ]]; then + echo "PASS contract-a" +else + echo "FAIL contract-a: rc=$rc stderr=$(printf '%s' "$err" | oneline)" +fi + +# (b) first run onboards, then execs `openclaw --version` +export MODEL_BASE_URL="$base_url" MODEL_ID=smoke-model CUSTOM_API_KEY=smoke-placeholder +out=$(openclaw-start --version 2>/tmp/b.err); rc=$? +if [[ $rc -eq 0 && "$out" == "OpenClaw "* ]] && grep -q 'openclaw-start: wrote' /tmp/b.err; then + echo "PASS contract-b" +else + echo "FAIL contract-b: rc=$rc out=$(printf '%s' "$out" | oneline) stderr=$(oneline &1) +if [[ "$c_out" == "OK" ]]; then + echo "PASS contract-c" +else + echo "FAIL contract-c: $(printf '%s' "$c_out" | oneline)" +fi + +# (d) a second run leaves the config untouched and does not onboard again +before=$(sha256sum "$cfg" 2>/dev/null | cut -d' ' -f1) +out=$(openclaw-start --version 2>/tmp/d.err); rc=$? +after=$(sha256sum "$cfg" 2>/dev/null | cut -d' ' -f1) +if [[ $rc -eq 0 && -n "$before" && "$before" == "$after" ]] && ! grep -q 'openclaw-start: wrote' /tmp/d.err; then + echo "PASS contract-d" +else + echo "FAIL contract-d: rc=$rc before=$before after=$after stderr=$(oneline &1); then + echo "PASS contract-e" +else + echo "FAIL contract-e: $(printf '%s' "$e_out" | oneline)" +fi +CONTRACT +); then + contract_rc=0 +else + contract_rc=$? +fi +for sub in a b c d e; do + line=$(printf '%s\n' "${contract_out}" | grep -E "^(PASS|FAIL) contract-${sub}(:|$)" | head -n1 || true) + if [[ "${line}" == "PASS contract-${sub}" ]]; then + pass "contract-${sub}" + elif [[ -n "${line}" ]]; then + fail "contract-${sub}" "${line#FAIL contract-"${sub}": }" + else + fail "contract-${sub}" "no result (container rc=${contract_rc}): $(printf '%s' "${contract_out}" | tr '\n' ' ' | cut -c1-400)" + fi +done + +# --- login-shell (K8s exec-style login shell picks up profile.d) --------- +# shellcheck disable=SC2016 # runs inside the container's shell, not the host's +if login_out=$(podman_run --user 51234:51234 --passwd=false "${image}" \ + env -i HOME=/sandbox PATH=/usr/bin:/bin bash -lc \ + 'printf "%s|%s|%s" "$OPENCLAW_NO_AUTO_UPDATE" "$DO_NOT_TRACK" "$(command -v openclaw)"' 2>/dev/null); then + login_rc=0 +else + login_rc=$? +fi +if [[ ${login_rc} -eq 0 && "${login_out}" == "1|1|/usr/local/bin/openclaw" ]]; then + pass "login-shell" +else + fail "login-shell" "rc=${login_rc} out='${login_out}'" +fi + +# --- missing-env (default user, no MODEL_* env) --------------------------- +if missing_out=$(podman_run "${image}" openclaw-start 2>&1); then + missing_rc=0 +else + missing_rc=$? +fi +if [[ ${missing_rc} -eq 2 && "${missing_out}" == *MODEL_BASE_URL* ]]; then + pass "missing-env" +else + fail "missing-env" "rc=${missing_rc} out='${missing_out}'" +fi + +if [[ ${failures} -gt 0 ]]; then + echo "smoke-test: ${failures} check(s) failed" >&2 + exit 1 +fi +echo "smoke-test: all checks passed" diff --git a/renovate.json b/renovate.json index d2c6f5dffe..527dc680a3 100644 --- a/renovate.json +++ b/renovate.json @@ -1,6 +1,15 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "enabledManagers": ["rpm-lockfile", "tekton"], + "enabledManagers": ["rpm-lockfile", "tekton", "custom.regex"], + "customManagers": [ + { + "customType": "regex", + "description": "Track the digest-pinned nodejs-24-minimal base of the OpenClaw image (npm inputs follow the AIPCC agentic image instead)", + "managerFilePatterns": ["/^deploy/docker/Dockerfile\\.konflux\\.openclaw$/"], + "matchStrings": ["ARG NODEJS_IMAGE=(?[^:\\s]+):(?[^@\\s]+)@(?sha256:[a-f0-9]{64})"], + "datasourceTemplate": "docker" + } + ], "packageRules": [ { "description": "Automerge non-major dependency updates", From 07a486d751876fdc2bb2ffde74c1ed3adc8b1421 Mon Sep 17 00:00:00 2001 From: Eric Curtin Date: Wed, 30 Sep 2026 18:50:04 +0000 Subject: [PATCH 04/33] fix(cli): accept sandbox name before -- in exec (#3901) * fix(cli): accept sandbox name before -- in exec Closes #3882 Signed-off-by: Eric Curtin * fix(cli): define exec grammar in clap Signed-off-by: Eric Curtin * docs(sandboxes): remove exec overview change from PR Signed-off-by: Drew Newberry --------- Signed-off-by: Eric Curtin Signed-off-by: Drew Newberry Co-authored-by: Drew Newberry --- crates/openshell-cli/src/main.rs | 98 ++++++++++++++++++++++++++++++-- skills/openshell-cli/SKILL.md | 6 +- 2 files changed, 99 insertions(+), 5 deletions(-) diff --git a/crates/openshell-cli/src/main.rs b/crates/openshell-cli/src/main.rs index f656fd8833..856113ab73 100644 --- a/crates/openshell-cli/src/main.rs +++ b/crates/openshell-cli/src/main.rs @@ -1674,13 +1674,18 @@ enum SandboxCommands { /// For interactive shell sessions, use `sandbox connect` instead. /// /// Examples: + /// openshell sandbox exec my-sandbox -- ls -la /workspace /// openshell sandbox exec --name my-sandbox -- ls -la /workspace /// openshell sandbox exec -n my-sandbox --workdir /app -- python script.py /// echo "hello" | openshell sandbox exec -n my-sandbox -- cat #[command(help_template = LEAF_HELP_TEMPLATE, next_help_heading = "FLAGS")] Exec { /// Sandbox name (defaults to last-used sandbox). - #[arg(long, short = 'n', add = ArgValueCompleter::new(completers::complete_sandbox_names))] + #[arg(add = ArgValueCompleter::new(completers::complete_sandbox_names))] + sandbox: Option, + + /// Sandbox name; same as the positional argument. + #[arg(long, short = 'n', conflicts_with = "sandbox", add = ArgValueCompleter::new(completers::complete_sandbox_names))] name: Option, /// Working directory inside the sandbox. @@ -1717,8 +1722,8 @@ enum SandboxCommands { #[arg(long = "env", value_name = "KEY=VALUE")] envs: Vec, - /// Command and arguments to execute. - #[arg(required = true, trailing_var_arg = true, allow_hyphen_values = true)] + /// Command and arguments to execute, after `--`. + #[arg(required = true, last = true)] command: Vec, }, @@ -3569,6 +3574,7 @@ async fn run_async() -> Result<()> { let _ = save_last_sandbox(&ctx.name, &cli.workspace, &name); } SandboxCommands::Exec { + sandbox, name, workdir, timeout, @@ -3578,7 +3584,8 @@ async fn run_async() -> Result<()> { command, no_login_shell, } => { - let name = resolve_sandbox_name(name, &ctx.name, &cli.workspace)?; + let name = + resolve_sandbox_name(name.or(sandbox), &ctx.name, &cli.workspace)?; // Resolve --tty / --no-tty into an Option override. let tty_override = if no_tty { Some(false) @@ -4408,6 +4415,89 @@ mod tests { assert_eq!(provider, "work-github"); } + #[test] + fn exec_grammar_requires_separator_before_remote_command() { + use clap::error::ErrorKind; + + // Returns (target, command, tty) or the clap error kind. + let parse = |args: &[&str]| { + let mut argv = vec!["openshell", "sandbox", "exec"]; + argv.extend(args); + let cli = Cli::try_parse_from(argv).map_err(|e| e.kind())?; + let Some(Commands::Sandbox { + command: + Some(SandboxCommands::Exec { + sandbox, + name, + command, + tty, + .. + }), + }) = cli.command + else { + panic!("expected sandbox exec command"); + }; + Ok::<_, ErrorKind>((name.or(sandbox), command, tty)) + }; + let check = |args: &[&str], target: Option<&str>, command: &[&str], tty: bool| { + let got = parse(args).unwrap_or_else(|kind| panic!("{args:?} failed: {kind:?}")); + let command = command.iter().map(ToString::to_string).collect(); + assert_eq!(got, (target.map(str::to_string), command, tty), "{args:?}"); + }; + + check( + &["a", "--", "echo", "hi"], + Some("a"), + &["echo", "hi"], + false, + ); + check(&["-n", "a", "--", "echo"], Some("a"), &["echo"], false); + check(&["--name", "a", "--", "echo"], Some("a"), &["echo"], false); + check(&["--", "echo", "hi"], None, &["echo", "hi"], false); + // Flags on either side of the positional target. + check(&["--tty", "a", "--", "echo"], Some("a"), &["echo"], true); + check(&["a", "--tty", "--", "echo"], Some("a"), &["echo"], true); + check( + &["-n", "a", "--tty", "--", "echo"], + Some("a"), + &["echo"], + true, + ); + // Hyphenated remote args are opaque. + check(&["a", "--", "ls", "-la"], Some("a"), &["ls", "-la"], false); + check(&["a", "--", "--tty"], Some("a"), &["--tty"], false); + check(&["--", "-n", "x"], None, &["-n", "x"], false); + // An inner delimiter belongs to the remote command. + let git = ["git", "log", "--", "path"]; + check( + &["a", "--", "git", "log", "--", "path"], + Some("a"), + &git, + false, + ); + check(&["--", "git", "log", "--", "path"], None, &git, false); + + let err: &[(&[&str], ErrorKind)] = &[ + // Target given twice. + (&["-n", "a", "b", "--", "echo"], ErrorKind::ArgumentConflict), + (&["b", "-n", "a", "--", "echo"], ErrorKind::ArgumentConflict), + // Missing `--`. + (&["a", "echo", "hi"], ErrorKind::UnknownArgument), + (&["a", "--tty", "echo"], ErrorKind::UnknownArgument), + (&["-n", "a", "echo", "hi"], ErrorKind::UnknownArgument), + (&["git", "log"], ErrorKind::UnknownArgument), + (&["a"], ErrorKind::MissingRequiredArgument), + (&[], ErrorKind::MissingRequiredArgument), + // Missing remote command. + (&["a", "--"], ErrorKind::MissingRequiredArgument), + (&["-n", "a", "--"], ErrorKind::MissingRequiredArgument), + (&["--"], ErrorKind::MissingRequiredArgument), + ]; + for (args, kind) in err { + assert_eq!(parse(args).map(|_| ()), Err(*kind), "{args:?}"); + } + } + #[test] fn provider_readiness_commands_have_bounded_waits_and_structured_output() { for action in ["attach", "detach", "status"] { diff --git a/skills/openshell-cli/SKILL.md b/skills/openshell-cli/SKILL.md index c9440dfaad..572eadc96d 100644 --- a/skills/openshell-cli/SKILL.md +++ b/skills/openshell-cli/SKILL.md @@ -411,10 +411,14 @@ within it. ### Execute a non-interactive command ```bash -openshell sandbox exec --name my-sandbox --workdir /workspace -- ls -la +openshell sandbox exec my-sandbox --workdir /workspace -- ls -la openshell sandbox exec --name my-sandbox --env MODE=test -- cargo test ``` +The sandbox is a positional name or `--name`, not both; omit it to use the +last-used sandbox. `--` is required and everything after it is the remote +command, so put options such as `--tty` before it. + `sandbox exec` starts an independent sibling process and streams output. After stdout and stderr drain, it returns the remote command's exit code if delivery succeeds. Output delivery failure instead returns exit code 74, even when the From 374c035962dada63852ec1dea073fea618ed76c5 Mon Sep 17 00:00:00 2001 From: Shiju Date: Wed, 30 Sep 2026 20:10:04 +0000 Subject: [PATCH 05/33] fix(network): refuse protocol upgrades on GraphQL endpoints (#3841) * fix(network): refuse protocol upgrades on GraphQL endpoints Refuse Upgrade headers before forwarding GraphQL-over-HTTP requests. Share the protocol refusal table with JSON-RPC and MCP, and close unexpected protocol switches before relaying frames. Keep GraphQL-over-WebSocket inspection on separate WebSocket endpoints. Cover upgrade refusal, audit mode, subscription handshakes, and ordinary HTTP and WebSocket controls. Update the current policy documentation. Signed-off-by: Shiju * fix(network): refuse GraphQL upgrades before reading bodies Validate the HTTP head and endpoint authority before upgrade refusal, then inspect ordinary GraphQL bodies. Preserve missing-authority credential rejection after body inspection. Signed-off-by: Shiju --------- Signed-off-by: Shiju --- .../src/l7/relay.rs | 481 ++++++++++++++++-- .../src/l7/rest.rs | 88 +++- .../openshell-supervisor-network/src/proxy.rs | 141 ++++- .../how-it-works/policies/manage-policies.mdx | 2 +- docs/how-it-works/policies/network-rules.mdx | 2 + docs/how-it-works/policies/schema.mdx | 2 +- 6 files changed, 638 insertions(+), 78 deletions(-) diff --git a/crates/openshell-supervisor-network/src/l7/relay.rs b/crates/openshell-supervisor-network/src/l7/relay.rs index 0aa453258f..d0351e2153 100644 --- a/crates/openshell-supervisor-network/src/l7/relay.rs +++ b/crates/openshell-supervisor-network/src/l7/relay.rs @@ -1387,18 +1387,19 @@ where websocket_permessage_deflate, websocket_subprotocol, } => { - // JSON-RPC and MCP rules apply to individual HTTP requests. - // No current path forwards upgrade headers for these - // protocols: the request-side refusal rejects them, and - // request middleware cannot add upgrade or connection - // headers. If a later change lets such a request reach an - // upstream that answers `101`, close instead of relaying - // frames that no rule would inspect. - if config.protocol.is_jsonrpc_family() { + // Protocols whose rules apply to individual HTTP requests + // never upgrade (see `upgrade_refusal_for_protocol`). No + // current path forwards upgrade headers for them: the + // request-side refusal rejects them, and request + // middleware cannot add upgrade or connection headers. If + // a later change lets such a request reach an upstream + // that answers `101`, close instead of relaying frames + // that no rule would inspect. + if crate::l7::rest::upgrade_refusal_for_protocol(config.protocol).is_some() { warn!( host = %ctx.host, port = ctx.port, - "closing JSON-RPC connection after unexpected protocol upgrade" + "closing per-request L7 connection after unexpected protocol upgrade" ); if let Some(session) = middleware_session.take() { session @@ -2593,23 +2594,56 @@ where C: AsyncRead + AsyncWrite + Unpin + Send, U: AsyncRead + AsyncWrite + Unpin + Send, { + let provider = + crate::l7::rest::RestProvider::with_options(crate::l7::path::CanonicalizeOptions { + allow_encoded_slash: config.allow_encoded_slash, + ..Default::default() + }); + loop { if close_if_stale(engine.generation_guard(), ctx) { return Ok(()); } - let parsed = match crate::l7::graphql::parse_graphql_http_request( + // Validate the head, including body framing, before deciding whether + // this endpoint can inspect the requested protocol. Upgrade refusal + // must not wait for a body or depend on its inspection size limit. + let mut req = match provider.parse_request(client).await { + Ok(Some(req)) => req, + Ok(None) => return Ok(()), + Err(e) => { + if is_benign_connection_error(&e) { + debug!( + host = %ctx.host, + port = ctx.port, + error = %e, + "GraphQL L7 connection closed" + ); + } else { + let detail = + parse_rejection_detail(&e.to_string(), ParseRejectionMode::L7Endpoint); + emit_parse_rejection(ctx, &detail, "l7-graphql"); + } + return Ok(()); + } + }; + + if !request_authority_matches_endpoint(&req, ctx) { + reject_request_authority_mismatch(client, ctx, &req.action).await?; + return Ok(()); + } + if deny_unsupported_upgrade_if_requested(&req, config, ctx, None, client).await? { + return Ok(()); + } + + let graphql_info = match crate::l7::graphql::inspect_graphql_request( client, + &mut req, config.graphql_max_body_bytes, - crate::l7::path::CanonicalizeOptions { - allow_encoded_slash: config.allow_encoded_slash, - ..Default::default() - }, ) .await { - Ok(Some(parsed)) => parsed, - Ok(None) => return Ok(()), + Ok(info) => info, Err(e) => { if is_benign_connection_error(&e) { debug!( @@ -2627,15 +2661,13 @@ where } }; - let req = parsed.request; - let graphql_info = parsed.info; + // Inspection appends the body to raw_header. An HTTP/1.0 request + // without Host must still be denied if that body contains reserved + // credential markers, so repeat the authority check on the full request. if !request_authority_matches_endpoint(&req, ctx) { reject_request_authority_mismatch(client, ctx, &req.action).await?; return Ok(()); } - if deny_unsupported_upgrade_if_requested(&req, config, ctx, None, client).await? { - return Ok(()); - } if close_if_stale(engine.generation_guard(), ctx) { return Ok(()); @@ -2810,25 +2842,20 @@ where ); return Ok(()); } - RelayOutcome::Upgraded { - overflow, - websocket_permessage_deflate, - .. - } => { - let options = UpgradeRelayOptions { - assembly_budget: Some( - crate::l7::websocket::WebSocketAssemblyBudget::default(), - ), - websocket: WebSocketUpgradeBehavior { - permessage_deflate: websocket_permessage_deflate, - ..Default::default() - }, - ..Default::default() - }; - return handle_upgrade( - client, upstream, overflow, &ctx.host, ctx.port, options, - ) - .await; + RelayOutcome::Upgraded { .. } => { + // GraphQL rules apply to individual HTTP requests. No + // current path forwards upgrade headers here: the + // request-side refusal rejects them, and request + // middleware cannot add upgrade or connection headers. If + // a later change lets such a request reach an upstream + // that answers `101`, close instead of relaying frames + // that no GraphQL rule would inspect. + warn!( + host = %ctx.host, + port = ctx.port, + "closing GraphQL connection after unexpected protocol upgrade" + ); + return Ok(()); } } } else { @@ -9280,9 +9307,56 @@ network_policies: - {{ path: /usr/bin/python3 }} "# ); - let engine = OpaEngine::from_strings(TEST_POLICY, &data).unwrap(); + two_endpoint_route_configs(&data, "mcp.example.test", "shared_api") + } + + /// Builds per-request route selection for a GraphQL endpoint at + /// `/graphql` that allows only `query { viewer }`, and a REST endpoint at + /// `/api/**`, on the same host and port. + fn graphql_and_rest_route_configs( + enforcement: &str, + ) -> (Vec, TunnelPolicyEngine, L7EvalContext) { + let data = format!( + r#" +network_policies: + shared_graphql: + name: shared_graphql + endpoints: + - host: graphql.example.test + port: 8000 + path: "/graphql" + protocol: graphql + enforcement: {enforcement} + rules: + - allow: + operation_type: query + fields: [viewer] + - host: graphql.example.test + port: 8000 + path: "/api/**" + protocol: rest + enforcement: enforce + rules: + - allow: + method: GET + path: "/api/**" + binaries: + - {{ path: /usr/bin/python3 }} +"# + ); + two_endpoint_route_configs(&data, "graphql.example.test", "shared_graphql") + } + + /// Loads `data` and returns the two L7 configs that share `host:8000` + /// for `/usr/bin/python3`, with a matching tunnel engine and context. + fn two_endpoint_route_configs( + data: &str, + host: &str, + policy_name: &str, + ) -> (Vec, TunnelPolicyEngine, L7EvalContext) { + let engine = OpaEngine::from_strings(TEST_POLICY, data).unwrap(); let input = NetworkInput { - host: "mcp.example.test".into(), + host: host.into(), port: 8000, binary_path: PathBuf::from("/usr/bin/python3"), binary_sha256: "unused".into(), @@ -9299,10 +9373,10 @@ network_policies: assert_eq!(configs.len(), 2, "both endpoints must share the route"); let tunnel_engine = engine.clone_engine_for_tunnel(generation).unwrap(); let ctx = L7EvalContext { - host: "mcp.example.test".into(), + host: host.into(), port: 8000, request_default_port: Some(8000), - policy_name: "shared_api".into(), + policy_name: policy_name.into(), binary_path: "/usr/bin/python3".into(), ancestors: vec![], cmdline_paths: vec![], @@ -9402,12 +9476,23 @@ network_policies: } fn assert_upgrade_denied_before_forwarding(scenario: &UpgradeScenario) { + assert_upgrade_refused_before_forwarding( + scenario, + UNALLOWED_TOOL_CALL, + crate::l7::rest::UNSUPPORTED_JSONRPC_UPGRADE_DETAIL, + ); + } + + /// Asserts that the relay answered the upgrade with the `detail` refusal + /// and that neither the request nor `frame` reached the upstream. + fn assert_upgrade_refused_before_forwarding( + scenario: &UpgradeScenario, + frame: &[u8], + detail: &str, + ) { assert!( - !contains_bytes( - &scenario.upstream_seen, - &masked_text_frame(UNALLOWED_TOOL_CALL) - ), - "an uninspected tools/call frame reached the upstream" + !contains_bytes(&scenario.upstream_seen, &masked_text_frame(frame)), + "an uninspected frame reached the upstream" ); assert!( scenario.upstream_seen.is_empty(), @@ -9420,10 +9505,7 @@ network_policies: scenario.response ); assert!( - scenario.body.contains("\"unsupported_l7_protocol\"") - && scenario - .body - .contains(crate::l7::rest::UNSUPPORTED_JSONRPC_UPGRADE_DETAIL), + scenario.body.contains("\"unsupported_l7_protocol\"") && scenario.body.contains(detail), "expected the upgrade refusal, got: {}", scenario.body ); @@ -9549,8 +9631,8 @@ network_policies: #[tokio::test] async fn route_selected_rest_websocket_upgrade_still_relays_beside_mcp() { - // The refusal is scoped to JSON-RPC-family endpoints: a REST upgrade - // on the same host and port keeps its documented raw relay. + // The refusal follows the selected endpoint's protocol: a REST + // upgrade on the same host and port keeps its documented raw relay. let (configs, tunnel_engine, ctx) = jsonrpc_and_rest_route_configs("mcp", "enforce"); let frame = br#"{"type":"ping"}"#; let scenario = run_upgrade_scenario( @@ -9615,6 +9697,295 @@ network_policies: let _ = relay.await; } + /// A GraphQL-over-WebSocket message that no GraphQL fixture allows. + const UNALLOWED_GRAPHQL_MUTATION: &[u8] = + br#"{"id":"1","type":"subscribe","payload":{"query":"mutation { deleteRepository }"}}"#; + + /// A GET whose query the GraphQL fixtures allow, plus WebSocket upgrade + /// headers. + const GRAPHQL_WEBSOCKET_UPGRADE_REQUEST: &[u8] = b"GET /graphql?query=%7Bviewer%7D HTTP/1.1\r\nHost: graphql.example.test:8000\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\n\r\n"; + + fn assert_graphql_upgrade_refused_before_forwarding(scenario: &UpgradeScenario) { + assert_upgrade_refused_before_forwarding( + scenario, + UNALLOWED_GRAPHQL_MUTATION, + crate::l7::rest::UNSUPPORTED_GRAPHQL_UPGRADE_DETAIL, + ); + } + + #[tokio::test] + async fn single_endpoint_graphql_websocket_upgrade_is_denied_before_forwarding() { + let (config, tunnel_engine, ctx) = graphql_test_relay_context(); + let scenario = run_upgrade_scenario( + GRAPHQL_WEBSOCKET_UPGRADE_REQUEST, + UNALLOWED_GRAPHQL_MUTATION, + move |mut client, mut upstream| { + tokio::spawn(async move { + relay_with_inspection(&config, tunnel_engine, &mut client, &mut upstream, &ctx) + .await + }) + }, + ) + .await; + assert_graphql_upgrade_refused_before_forwarding(&scenario); + } + + #[tokio::test(start_paused = true)] + async fn single_endpoint_graphql_upgrade_is_refused_before_body_read() { + // Send only the complete head. Neither an oversized declaration nor + // an incomplete allowed-size body may delay the upgrade refusal. + for content_length in [65537, 16] { + for enforcement in [EnforcementMode::Enforce, EnforcementMode::Audit] { + for upgrade in ["websocket", "custom"] { + let (mut config, engine, ctx) = graphql_test_relay_context(); + config.enforcement = enforcement; + assert_eq!(config.graphql_max_body_bytes, 65536); + let request = format!( + "POST /graphql HTTP/1.1\r\nHost: graphql.example.test:8000\r\nContent-Type: application/json\r\nConnection: Upgrade\r\nUpgrade: {upgrade}\r\nContent-Length: {content_length}\r\n\r\n" + ); + let started = tokio::time::Instant::now(); + let scenario = run_upgrade_scenario( + request.as_bytes(), + UNALLOWED_GRAPHQL_MUTATION, + move |mut client, mut upstream| { + tokio::spawn(async move { + relay_with_inspection( + &config, + engine, + &mut client, + &mut upstream, + &ctx, + ) + .await + }) + }, + ) + .await; + assert_graphql_upgrade_refused_before_forwarding(&scenario); + assert_eq!(started.elapsed(), std::time::Duration::ZERO); + } + } + } + } + + #[tokio::test(start_paused = true)] + async fn single_endpoint_graphql_upgrade_preserves_head_validation() { + // Framing errors are rejected by the HTTP parser, and authority + // mismatches retain their specific denial before upgrade handling. + for (host, framing, expected) in [ + ( + "graphql.example.test:8000", + "Content-Length: 16\r\nTransfer-Encoding: chunked\r\n", + "", + ), + ( + "graphql.example.test:8000", + "Content-Length: 16\r\nContent-Length: 17\r\n", + "", + ), + ( + "other.example.test:8000", + "Content-Length: 16\r\n", + "request_authority_mismatch", + ), + ] { + let (config, engine, ctx) = graphql_test_relay_context(); + let request = format!( + "POST /graphql HTTP/1.1\r\nHost: {host}\r\nConnection: Upgrade\r\nUpgrade: custom\r\n{framing}\r\n" + ); + let scenario = run_upgrade_scenario( + request.as_bytes(), + UNALLOWED_GRAPHQL_MUTATION, + move |mut client, mut upstream| { + tokio::spawn(async move { + relay_with_inspection(&config, engine, &mut client, &mut upstream, &ctx) + .await + }) + }, + ) + .await; + assert!(scenario.upstream_seen.is_empty()); + if expected.is_empty() { + assert!(scenario.response.is_empty()); + } else { + assert!(scenario.response.starts_with("HTTP/1.1 403")); + assert!(scenario.body.contains(expected)); + } + } + } + + #[tokio::test(start_paused = true)] + async fn single_endpoint_graphql_post_still_inspects_body() { + for (field, host, token) in [ + ("viewer", "graphql.example.test:8000", ""), + ("admin", "graphql.example.test:8000", ""), + ("viewer", "", ""), + ("viewer", "", "openshell:resolve:env:v1_API_TOKEN"), + ] { + let (config, engine, ctx) = graphql_test_relay_context(); + let (mut app, mut client) = tokio::io::duplex(8192); + let (mut relay_upstream, mut upstream) = tokio::io::duplex(8192); + let relay = tokio::spawn(async move { + relay_with_inspection(&config, engine, &mut client, &mut relay_upstream, &ctx).await + }); + let body = format!(r#"{{"query":"{{{field}}}","variables":{{"token":"{token}"}}}}"#); + let (version, authority) = if host.is_empty() { + ("HTTP/1.0", String::new()) + } else { + ("HTTP/1.1", format!("Host: {host}\r\n")) + }; + let request = format!( + "POST /graphql {version}\r\n{authority}Content-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", + body.len() + ); + app.write_all(request.as_bytes()).await.unwrap(); + let mut forwarded = Vec::new(); + if field == "viewer" && token.is_empty() { + let headers = tokio::time::timeout( + std::time::Duration::from_secs(1), + read_http_headers(&mut upstream), + ) + .await + .expect("allowed POST head must reach upstream"); + assert!(headers.starts_with(format!("POST /graphql {version}\r\n").as_bytes())); + let mut bytes = vec![0; body.len()]; + tokio::time::timeout( + std::time::Duration::from_secs(1), + upstream.read_exact(&mut bytes), + ) + .await + .expect("allowed POST must reach upstream") + .unwrap(); + assert_eq!(bytes, body.as_bytes()); + } else { + let mut response = Vec::new(); + tokio::time::timeout( + std::time::Duration::from_secs(1), + app.read_to_end(&mut response), + ) + .await + .expect("unlisted field or credential marker without Host must be denied") + .unwrap(); + assert!(response.starts_with(b"HTTP/1.1 403")); + if !token.is_empty() { + assert!( + String::from_utf8_lossy(&response).contains("request_authority_mismatch") + ); + } + upstream.read_to_end(&mut forwarded).await.unwrap(); + assert!(forwarded.is_empty()); + } + relay.abort(); + let _ = relay.await; + } + } + + #[tokio::test] + async fn route_selected_graphql_websocket_upgrade_is_denied_before_forwarding() { + // Audit mode forwards requests that policy would deny, so the upgrade + // refusal must not depend on the policy decision. + for enforcement in ["enforce", "audit"] { + let (configs, tunnel_engine, ctx) = graphql_and_rest_route_configs(enforcement); + let scenario = run_upgrade_scenario( + GRAPHQL_WEBSOCKET_UPGRADE_REQUEST, + UNALLOWED_GRAPHQL_MUTATION, + move |mut client, mut upstream| { + tokio::spawn(async move { + relay_with_route_selection( + &configs, + tunnel_engine, + &mut client, + &mut upstream, + &ctx, + ) + .await + }) + }, + ) + .await; + assert_graphql_upgrade_refused_before_forwarding(&scenario); + } + } + + #[tokio::test] + async fn route_selected_audit_graphql_upgrade_with_denied_query_is_refused() { + // Audit mode forwards a query the policy denies. The refusal must + // still fire, because it runs before the policy decision. + let (configs, tunnel_engine, ctx) = graphql_and_rest_route_configs("audit"); + let scenario = run_upgrade_scenario( + b"GET /graphql?query=%7Badmin%7D HTTP/1.1\r\nHost: graphql.example.test:8000\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\n\r\n", + UNALLOWED_GRAPHQL_MUTATION, + move |mut client, mut upstream| { + tokio::spawn(async move { + relay_with_route_selection( + &configs, + tunnel_engine, + &mut client, + &mut upstream, + &ctx, + ) + .await + }) + }, + ) + .await; + assert_graphql_upgrade_refused_before_forwarding(&scenario); + } + + #[tokio::test] + async fn single_endpoint_graphql_subscription_handshake_gets_upgrade_refusal() { + // A standard GraphQL-over-WebSocket handshake carries no query. It + // must receive the refusal that names the supported alternative, not + // a policy denial that suggests adding a rule. + let (config, tunnel_engine, ctx) = graphql_test_relay_context(); + let scenario = run_upgrade_scenario( + b"GET /graphql HTTP/1.1\r\nHost: graphql.example.test:8000\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Protocol: graphql-transport-ws\r\n\r\n", + UNALLOWED_GRAPHQL_MUTATION, + move |mut client, mut upstream| { + tokio::spawn(async move { + relay_with_inspection(&config, tunnel_engine, &mut client, &mut upstream, &ctx) + .await + }) + }, + ) + .await; + assert_graphql_upgrade_refused_before_forwarding(&scenario); + } + + #[tokio::test] + async fn route_selected_graphql_query_without_upgrade_is_still_forwarded() { + let (configs, tunnel_engine, ctx) = graphql_and_rest_route_configs("enforce"); + let (mut app, mut relay_client) = tokio::io::duplex(8192); + let (mut relay_upstream, mut upstream) = tokio::io::duplex(8192); + let relay = tokio::spawn(async move { + relay_with_route_selection( + &configs, + tunnel_engine, + &mut relay_client, + &mut relay_upstream, + &ctx, + ) + .await + }); + + app.write_all( + b"GET /graphql?query=%7Bviewer%7D HTTP/1.1\r\nHost: graphql.example.test:8000\r\n\r\n", + ) + .await + .unwrap(); + let forwarded = tokio::time::timeout( + std::time::Duration::from_secs(2), + read_http_headers(&mut upstream), + ) + .await + .expect("allowed GraphQL GET should reach the upstream"); + let forwarded = String::from_utf8_lossy(&forwarded); + assert!(forwarded.starts_with("GET /graphql?query=%7Bviewer%7D HTTP/1.1\r\n")); + assert!(!forwarded.to_ascii_lowercase().contains("upgrade")); + relay.abort(); + let _ = relay.await; + } + fn masked_text_frame(payload: &[u8]) -> Vec { let mask = [0x11, 0x22, 0x33, 0x44]; assert!( diff --git a/crates/openshell-supervisor-network/src/l7/rest.rs b/crates/openshell-supervisor-network/src/l7/rest.rs index ff8a54d9da..cb7ed28e78 100644 --- a/crates/openshell-supervisor-network/src/l7/rest.rs +++ b/crates/openshell-supervisor-network/src/l7/rest.rs @@ -86,6 +86,7 @@ pub(crate) const UNSUPPORTED_H2C_UPGRADE_DETAIL: &str = "HTTP/2 cleartext upgrade (h2c) is not supported for L7-inspected endpoints"; pub(crate) const UNSUPPORTED_JSONRPC_UPGRADE_DETAIL: &str = "HTTP upgrade is not supported for JSON-RPC or MCP endpoints"; +pub(crate) const UNSUPPORTED_GRAPHQL_UPGRADE_DETAIL: &str = "HTTP upgrade is not supported for GraphQL endpoints; serve GraphQL over WebSocket from a separate protocol: websocket endpoint on another path or port"; const MIN_HTTP2_PREFACE_DETECTION_BYTES: usize = 8; /// Idle timeout for `relay_until_eof`. If no data arrives within this window @@ -2436,9 +2437,8 @@ pub(crate) fn request_is_h2c_upgrade(raw_header: &[u8]) -> bool { /// Returns why an L7 endpoint using `protocol` must refuse this request's /// upgrade, or `None` when the request may continue. /// -/// Every inspected protocol refuses h2c. JSON-RPC and MCP policy applies to -/// individual HTTP requests, so after any protocol switch no rule would see -/// the messages; those endpoints refuse every request that carries an +/// Every inspected protocol refuses h2c. Protocols named by +/// `upgrade_refusal_for_protocol` refuse every request that carries an /// `Upgrade` header. Callers apply this before the L7 policy decision and /// regardless of enforcement mode, because an upgrade would end inspection /// rather than break a rule that audit mode could log. @@ -2449,10 +2449,30 @@ pub(crate) fn unsupported_upgrade_detail( if request_is_h2c_upgrade(raw_header) { return Some(UNSUPPORTED_H2C_UPGRADE_DETAIL); } - if protocol.is_jsonrpc_family() && request_has_upgrade_header(raw_header) { - return Some(UNSUPPORTED_JSONRPC_UPGRADE_DETAIL); + let refusal = upgrade_refusal_for_protocol(protocol)?; + request_has_upgrade_header(raw_header).then_some(refusal) +} + +/// Returns the refusal detail for a protocol whose policy applies only to +/// individual HTTP requests, or `None` for a protocol that may relay an +/// allowed upgrade. +/// +/// JSON-RPC, MCP and GraphQL rules inspect each HTTP request body or query. +/// After an upgrade the relay would copy frames that no rule of these +/// protocols evaluates, so they never upgrade; GraphQL over WebSocket is +/// served by separate `protocol: websocket` endpoints with GraphQL operation +/// rules. REST and WebSocket endpoints relay allowed upgrades, and SQL +/// endpoints keep their existing upgrade behavior. The match is exhaustive so +/// a new protocol must choose. +pub(crate) fn upgrade_refusal_for_protocol( + protocol: crate::l7::L7Protocol, +) -> Option<&'static str> { + use crate::l7::L7Protocol; + match protocol { + L7Protocol::JsonRpc | L7Protocol::Mcp => Some(UNSUPPORTED_JSONRPC_UPGRADE_DETAIL), + L7Protocol::Graphql => Some(UNSUPPORTED_GRAPHQL_UPGRADE_DETAIL), + L7Protocol::Rest | L7Protocol::Websocket | L7Protocol::Sql => None, } - None } /// Returns true when a request carries an `Upgrade` header, whatever its @@ -9129,14 +9149,13 @@ mod tests { } #[test] - fn unsupported_upgrade_detail_allows_ordinary_and_non_jsonrpc_requests() { + fn unsupported_upgrade_detail_allows_ordinary_requests_and_relaying_protocols() { let websocket = format!( "GET /ws HTTP/1.1\r\nHost: example.com\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: {VALID_WS_KEY}\r\nSec-WebSocket-Version: 13\r\n\r\n" ); for protocol in [ crate::l7::L7Protocol::Rest, crate::l7::L7Protocol::Websocket, - crate::l7::L7Protocol::Graphql, ] { assert_eq!( unsupported_upgrade_detail(websocket.as_bytes(), protocol), @@ -9155,7 +9174,11 @@ mod tests { b"GET /mcp HTTP/1.1\r\nHost: example.com\r\nConnection: Upgrade\r\n\r\n", ]; for raw in ordinary { - for protocol in [crate::l7::L7Protocol::JsonRpc, crate::l7::L7Protocol::Mcp] { + for protocol in [ + crate::l7::L7Protocol::JsonRpc, + crate::l7::L7Protocol::Mcp, + crate::l7::L7Protocol::Graphql, + ] { assert_eq!( unsupported_upgrade_detail(raw, protocol), None, @@ -9166,6 +9189,47 @@ mod tests { } } + #[test] + fn unsupported_upgrade_detail_refuses_upgrades_on_graphql() { + let requests = [ + format!( + "GET /graphql?query=%7Bviewer%7D HTTP/1.1\r\nHost: example.com\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: {VALID_WS_KEY}\r\nSec-WebSocket-Version: 13\r\n\r\n" + ), + format!( + "GET /graphql HTTP/1.1\r\nHost: example.com\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: {VALID_WS_KEY}\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Protocol: graphql-transport-ws\r\n\r\n" + ), + "POST /graphql HTTP/1.1\r\nHost: example.com\r\nUpgrade: custom\r\nConnection: upgrade\r\nContent-Length: 0\r\n\r\n" + .to_string(), + ]; + for raw in &requests { + assert_eq!( + unsupported_upgrade_detail(raw.as_bytes(), crate::l7::L7Protocol::Graphql), + Some(UNSUPPORTED_GRAPHQL_UPGRADE_DETAIL), + "{raw}" + ); + } + } + + #[test] + fn upgrade_refusal_for_protocol_names_every_per_request_protocol() { + use crate::l7::L7Protocol; + assert_eq!( + upgrade_refusal_for_protocol(L7Protocol::JsonRpc), + Some(UNSUPPORTED_JSONRPC_UPGRADE_DETAIL) + ); + assert_eq!( + upgrade_refusal_for_protocol(L7Protocol::Mcp), + Some(UNSUPPORTED_JSONRPC_UPGRADE_DETAIL) + ); + assert_eq!( + upgrade_refusal_for_protocol(L7Protocol::Graphql), + Some(UNSUPPORTED_GRAPHQL_UPGRADE_DETAIL) + ); + for protocol in [L7Protocol::Rest, L7Protocol::Websocket, L7Protocol::Sql] { + assert_eq!(upgrade_refusal_for_protocol(protocol), None, "{protocol:?}"); + } + } + #[test] fn unsupported_upgrade_detail_covers_every_relay_upgrade_check() { // The refusal must be at least as broad as both relay checks that can @@ -9198,7 +9262,11 @@ mod tests { client_requested_upgrade(raw) || request_is_websocket_upgrade(raw.as_bytes()), "fixture must be an upgrade to the relay: {raw}" ); - for protocol in [crate::l7::L7Protocol::JsonRpc, crate::l7::L7Protocol::Mcp] { + for protocol in [ + crate::l7::L7Protocol::JsonRpc, + crate::l7::L7Protocol::Mcp, + crate::l7::L7Protocol::Graphql, + ] { assert!( unsupported_upgrade_detail(raw.as_bytes(), protocol).is_some(), "{protocol:?} must refuse: {raw}" diff --git a/crates/openshell-supervisor-network/src/proxy.rs b/crates/openshell-supervisor-network/src/proxy.rs index 4f273817d7..ec567bc47b 100644 --- a/crates/openshell-supervisor-network/src/proxy.rs +++ b/crates/openshell-supervisor-network/src/proxy.rs @@ -6567,20 +6567,20 @@ async fn handle_forward_proxy( websocket_permessage_deflate, websocket_subprotocol, } => { - // JSON-RPC and MCP rules apply to individual HTTP requests. No - // current path forwards upgrade headers for these protocols: the - // request-side refusal rejects them, and request middleware cannot - // add upgrade or connection headers. If a later change lets such a - // request reach an upstream that answers `101`, close instead of - // relaying frames that no rule would inspect. - if forward_upgrade_config - .as_ref() - .is_some_and(|config| config.protocol.is_jsonrpc_family()) - { + // Protocols whose rules apply to individual HTTP requests never + // upgrade (see `upgrade_refusal_for_protocol`). No current path + // forwards upgrade headers for them: the request-side refusal + // rejects them, and request middleware cannot add upgrade or + // connection headers. If a later change lets such a request reach + // an upstream that answers `101`, close instead of relaying frames + // that no rule would inspect. + if forward_upgrade_config.as_ref().is_some_and(|config| { + crate::l7::rest::upgrade_refusal_for_protocol(config.protocol).is_some() + }) { warn!( host = %host_lc, port, - "closing forwarded JSON-RPC connection after unexpected protocol upgrade" + "closing forwarded per-request L7 connection after unexpected protocol upgrade" ); if let Some(session) = middleware_session.take() { session @@ -8777,6 +8777,125 @@ network_policies: ); } + #[tokio::test] + async fn forward_graphql_websocket_upgrade_is_denied_before_connecting_upstream() { + if !cfg!(target_os = "linux") { + eprintln!("skipping: handler identity binding requires /proc (Linux)"); + return; + } + let Some(upstream_ip) = non_loopback_test_ipv4() else { + eprintln!("skipping: no routable non-loopback IPv4 test address"); + return; + }; + + let upstream_listener = TcpListener::bind((upstream_ip, 0)) + .await + .expect("bind GraphQL upstream listener"); + let upstream_port = upstream_listener.local_addr().unwrap().port(); + let executable = std::env::current_exe().expect("current executable"); + let data = format!( + r#" +network_policies: + graphql-upstream: + name: graphql-upstream + endpoints: + - host: "{upstream_ip}" + port: {upstream_port} + path: /graphql + protocol: graphql + enforcement: enforce + rules: + - allow: + operation_type: query + fields: [viewer] + binaries: + - {{ path: "{executable}" }} +"#, + executable = executable.display(), + ); + let engine = Arc::new( + OpaEngine::from_strings(include_str!("../data/sandbox-policy.rego"), &data) + .expect("load GraphQL policy"), + ); + + let proxy_listener = TcpListener::bind("127.0.0.1:0") + .await + .expect("bind proxy listener"); + let proxy_address = proxy_listener.local_addr().unwrap(); + let target = format!("http://{upstream_ip}:{upstream_port}/graphql?query=%7Bviewer%7D"); + // A GET whose query the policy allows, plus WebSocket upgrade headers. + let request = format!( + "GET {target} HTTP/1.1\r\nHost: {upstream_ip}:{upstream_port}\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" + ); + let client = tokio::spawn(async move { + let mut socket = TcpStream::connect(proxy_address) + .await + .expect("connect proxy"); + let mut response = Vec::new(); + socket + .read_to_end(&mut response) + .await + .expect("read proxy response"); + response + }); + let (proxy_connection, _) = proxy_listener.accept().await.unwrap(); + let socket_addrs = proxy_connection + .peer_addr() + .ok() + .zip(proxy_connection.local_addr().ok()); + let stream: BoundaryDuplexStream = Box::new(proxy_connection); + let mut proxy_connection = tokio::io::BufReader::new(stream); + + tokio::time::timeout( + std::time::Duration::from_secs(30), + Box::pin(handle_forward_proxy( + "GET", + &target, + request.as_bytes(), + request.len(), + &mut proxy_connection, + None, + socket_addrs, + engine, + Arc::new(BinaryIdentityCache::new()), + Arc::new(AtomicU32::new(std::process::id())), + None, + AgentProposals::default(), + Arc::new(None), + Arc::new(None), + None, + None, + None, + None, + None, + None, + )), + ) + .await + .expect("refused upgrade must complete without an upstream response") + .expect("handle refused GraphQL WebSocket upgrade"); + drop(proxy_connection); + + let response = String::from_utf8(client.await.unwrap()).expect("UTF-8 response"); + assert!( + response.starts_with("HTTP/1.1 403"), + "the upgrade must be refused: {response}" + ); + assert!( + response.contains(crate::l7::rest::UNSUPPORTED_GRAPHQL_UPGRADE_DETAIL), + "the refusal must name the unsupported upgrade: {response}" + ); + assert!( + tokio::time::timeout( + std::time::Duration::from_millis(100), + upstream_listener.accept() + ) + .await + .is_err(), + "a refused upgrade must not establish an upstream connection" + ); + } + #[tokio::test] async fn plaintext_websocket_preflight_denial_does_not_connect_upstream() { if !cfg!(target_os = "linux") { diff --git a/docs/how-it-works/policies/manage-policies.mdx b/docs/how-it-works/policies/manage-policies.mdx index 89c7d44ce2..151c10414c 100644 --- a/docs/how-it-works/policies/manage-policies.mdx +++ b/docs/how-it-works/policies/manage-policies.mdx @@ -340,7 +340,7 @@ code in the response to find the cause: | `request_authority_mismatch` | The HTTP request's host or port differs from the connection's destination. | The client's `Host` header, including any non-default port, matches the connection. | | `credential_endpoint_mismatch` | A network rule allowed the request, but the provider credential is not bound to this destination. | The provider's profile endpoints or credential binding. Do not widen the network rule. | | `credential_placeholder_in_request_body` | The request body contains an invalid or revoked credential placeholder. | Remove the stale placeholder or restore the provider. | -| `unsupported_l7_protocol` | The request used a protocol or upgrade that the endpoint cannot inspect, such as h2c or an `Upgrade` header sent to an MCP or JSON-RPC endpoint. No rule can allow it. | Send the request without the upgrade, or allow WebSocket traffic through a separate `protocol: websocket` endpoint. | +| `unsupported_l7_protocol` | The request used a protocol or upgrade that the endpoint cannot inspect, such as h2c or an `Upgrade` header sent to a GraphQL, MCP, or JSON-RPC endpoint. No rule can allow it. | Send the request without the upgrade, or allow WebSocket traffic through a separate `protocol: websocket` endpoint. | ### A Change Fails to Load diff --git a/docs/how-it-works/policies/network-rules.mdx b/docs/how-it-works/policies/network-rules.mdx index 204ec633ef..811d920922 100644 --- a/docs/how-it-works/policies/network-rules.mdx +++ b/docs/how-it-works/policies/network-rules.mdx @@ -490,6 +490,8 @@ are application-specific, so review them against the service's schema before you rely on them. For deny rules, persisted queries, and GraphQL over WebSocket, refer to [GraphQL Rules](/how-it-works/policies/schema#graphql-rules). +GraphQL endpoints refuse requests that carry an `Upgrade` header with `403 Forbidden` in both `enforce` and `audit` mode. Serve GraphQL over WebSocket from a separate `protocol: websocket` endpoint on another path or port, with GraphQL operation rules. One path on a host and port can use only one of the two protocols. + With a GitHub provider attached, verify a REST read with `gh api zen` and a query with `gh api graphql -f query='{ viewer { login } }'`, then confirm that a mutation other than `createIssue` returns an OpenShell denial. diff --git a/docs/how-it-works/policies/schema.mdx b/docs/how-it-works/policies/schema.mdx index 8296abbc87..a190cd5a91 100644 --- a/docs/how-it-works/policies/schema.mdx +++ b/docs/how-it-works/policies/schema.mdx @@ -154,7 +154,7 @@ the gateway host. | Field | Type | Default | Description | |---|---|---|---| -| `protocol` | string | None | `rest`, `websocket`, `graphql`, `mcp`, or `json-rpc` for request inspection, or `tcp` for a native TCP connection. `mcp` and `json-rpc` endpoints refuse requests that carry an `Upgrade` header with `403`. Refer to [Connection and Request Checks](/how-it-works/policies/network-rules#connection-and-request-checks). | +| `protocol` | string | None | `rest`, `websocket`, `graphql`, `mcp`, or `json-rpc` for request inspection, or `tcp` for a native TCP connection. `graphql`, `mcp`, and `json-rpc` endpoints refuse requests that carry an `Upgrade` header with `403`. Refer to [Connection and Request Checks](/how-it-works/policies/network-rules#connection-and-request-checks). | | `tls` | string | Automatic | `skip` relays traffic without terminating TLS, so OpenShell cannot inspect it. Do not use it with a request protocol. | | `enforcement` | string | `audit` | `enforce` blocks requests that break the endpoint's rules. `audit` logs them and allows the request. | | `access` | string | None | Access preset: `read-only`, `read-write`, or `full`. Refer to [Access Presets](#access-presets). | From 912a077bd641272016fb8b2fd58209f6c7c6f194 Mon Sep 17 00:00:00 2001 From: Derek Carr Date: Wed, 30 Sep 2026 20:22:46 +0000 Subject: [PATCH 06/33] feat(service): add bearer authorization passthrough (#3796) * feat(service): add bearer authorization passthrough Signed-off-by: Derek Carr * docs(sdk): add service authorization migration guide Signed-off-by: Derek Carr * fix(server): remove stale version import Signed-off-by: Derek Carr * docs(upgrade): remove service authorization SDK guide Signed-off-by: Derek Carr * fix(e2e): relabel provider readiness TLS mount Signed-off-by: Derek Carr * test(e2e): stabilize exposed service routing Signed-off-by: Derek Carr * test(e2e): support HTTPS service routing Signed-off-by: Derek Carr --------- Signed-off-by: Derek Carr --- TESTING.md | 29 + crates/openshell-cli/src/main.rs | 112 +- crates/openshell-cli/src/run.rs | 62 +- .../sandbox_create_lifecycle_integration.rs | 31 + crates/openshell-sdk/README.md | 6 +- crates/openshell-sdk/src/client.rs | 6 + crates/openshell-sdk/src/lib.rs | 4 +- crates/openshell-sdk/src/types.rs | 21 + crates/openshell-sdk/tests/client_mock.rs | 9 +- crates/openshell-server/src/compute/mod.rs | 3 + .../src/grpc/mutation_replay/tests.rs | 19 +- crates/openshell-server/src/grpc/sandbox.rs | 58 +- crates/openshell-server/src/grpc/service.rs | 168 +- .../openshell-server/src/service_routing.rs | 291 ++- crates/openshell-server/src/storage_proto.rs | 40 +- docs/how-it-works/sandboxes/overview.mdx | 87 +- e2e/rust/Cargo.lock | 171 +- e2e/rust/Cargo.toml | 8 + e2e/rust/tests/provider_readiness.rs | 9 +- e2e/rust/tests/service_bearer_passthrough.rs | 344 ++++ proto/openshell.proto | 17 + python/openshell/__init__.py | 2 + python/openshell/sandbox.py | 15 + python/openshell/sandbox_test.py | 18 +- sdk/go/openshell/v1/fake/service.go | 2 +- .../v1/internal/converter/service.go | 26 +- .../v1/internal/converter/service_test.go | 44 +- sdk/go/openshell/v1/sandbox_client.go | 12 +- sdk/go/openshell/v1/sandbox_client_test.go | 8 +- sdk/go/openshell/v1/service.go | 17 +- sdk/go/openshell/v1/service_client.go | 17 +- sdk/go/openshell/v1/service_client_test.go | 20 +- sdk/go/openshell/v1/types/service.go | 32 +- sdk/go/proto/openshellv1/openshell.pb.go | 1628 +++++++++-------- sdk/typescript/src/client.test.ts | 38 +- sdk/typescript/src/client.ts | 20 + sdk/typescript/src/index.ts | 9 +- skills/openshell-cli/SKILL.md | 17 +- tests/suites/features/Cargo.lock | 171 +- 39 files changed, 2684 insertions(+), 907 deletions(-) create mode 100644 e2e/rust/tests/service_bearer_passthrough.rs diff --git a/TESTING.md b/TESTING.md index 59031f242f..bce0f36a62 100644 --- a/TESTING.md +++ b/TESTING.md @@ -178,6 +178,35 @@ Rust-based e2e tests that exercise the `openshell` CLI binary as a subprocess. They live in the `openshell-e2e` crate and use a shared harness for sandbox lifecycle management, output parsing, and cleanup. +Exposed service URLs use virtual hostnames for gateway routing. Host-side tests +must connect the TCP socket directly to a reachable gateway listener address, +normally loopback, and send the service URL authority in the HTTP `Host` +header. Do not resolve `*.openshell.localhost`; resolver support for arbitrary +`.localhost` subdomains varies across local and CI environments. + +Treat the advertised service URL scheme as authoritative. For HTTPS, use the +virtual service hostname for TLS SNI and the configured gateway trust roots. +When the listener requires mTLS, present the active gateway client identity; +the local e2e wrappers register these materials under +`$XDG_CONFIG_HOME/openshell/gateways/$OPENSHELL_GATEWAY/mtls/`. Do not downgrade +an HTTPS service URL to plaintext when dialing loopback. Parse the URL and load +TLS material before entering a readiness loop so permanent configuration +errors fail immediately. Retry only transient connection failures and +documented readiness responses, and include the last observation in timeout +diagnostics. + +Verify exposed-service tests in both the default local mode and the +CI-equivalent HTTPS mode: + +```shell +mise run e2e:rust +OPENSHELL_ENABLE_LOOPBACK_SERVICE_HTTP=false mise run e2e:rust +``` + +When more than one test needs this behavior, put the transport in the shared +Rust e2e harness and require callers to use it instead of duplicating DNS, +HTTP `Host`, TLS SNI, and mTLS handling. + Suites: - Common suite (`--features e2e`) - driver-neutral CLI behavior, sandbox lifecycle, sync, port forwarding, policy, and provider tests. diff --git a/crates/openshell-cli/src/main.rs b/crates/openshell-cli/src/main.rs index 856113ab73..ef40f79936 100644 --- a/crates/openshell-cli/src/main.rs +++ b/crates/openshell-cli/src/main.rs @@ -19,7 +19,7 @@ use openshell_bootstrap::{ use openshell_cli::completers; use openshell_cli::run; use openshell_cli::tls::TlsOptions; -use openshell_core::proto::GpuResourceRequirements; +use openshell_core::proto::{GpuResourceRequirements, ServiceAuthorizationMode}; /// Resolved gateway context: name + gateway endpoint. struct GatewayContext { @@ -770,6 +770,22 @@ enum OutputFormat { Json, } +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, ValueEnum)] +enum CliServiceAuthorizationMode { + #[default] + Strip, + BearerPassthrough, +} + +impl From for ServiceAuthorizationMode { + fn from(value: CliServiceAuthorizationMode) -> Self { + match value { + CliServiceAuthorizationMode::Strip => Self::Strip, + CliServiceAuthorizationMode::BearerPassthrough => Self::BearerPassthrough, + } + } +} + #[derive(Clone, Debug, ValueEnum)] enum CliProviderRefreshStrategy { Oauth2RefreshToken, @@ -1520,6 +1536,10 @@ enum SandboxCommands { )] expose: Option, + /// Handling for an incoming application Authorization header. + #[arg(long, value_enum, default_value_t, requires = "expose")] + expose_authorization_mode: CliServiceAuthorizationMode, + /// Allocate a pseudo-terminal for the remote command. /// Defaults to auto-detection (on when stdin and stdout are terminals). /// Use --tty to force a PTY even when auto-detection fails, or @@ -2369,6 +2389,10 @@ enum ServiceCommands { /// Service name. service: Option, + + /// Handling for an incoming application Authorization header. + #[arg(long, value_enum, default_value_t)] + authorization_mode: CliServiceAuthorizationMode, }, /// List exposed sandbox service endpoints. @@ -2914,6 +2938,7 @@ async fn run_async() -> Result<()> { sandbox, service, target_port, + authorization_mode, } => { let service = service.unwrap_or_default(); run::service_expose( @@ -2921,6 +2946,7 @@ async fn run_async() -> Result<()> { &sandbox, &service, target_port, + authorization_mode.into(), &cli.workspace, &tls, ) @@ -3324,6 +3350,7 @@ async fn run_async() -> Result<()> { policy, forward, expose, + expose_authorization_mode, tty, no_tty, detach, @@ -3421,6 +3448,7 @@ async fn run_async() -> Result<()> { policy: policy.as_deref(), forward, expose, + expose_authorization_mode: expose_authorization_mode.into(), command: &command, tty_override, auto_providers_override, @@ -6716,9 +6744,19 @@ mod tests { match cli.command { Some(Commands::Sandbox { - command: Some(SandboxCommands::Create { expose, detach, .. }), + command: + Some(SandboxCommands::Create { + expose, + expose_authorization_mode, + detach, + .. + }), }) => { assert_eq!(expose, Some(4500)); + assert_eq!( + expose_authorization_mode, + CliServiceAuthorizationMode::Strip + ); assert!(detach); } other => panic!("expected SandboxCommands::Create, got: {other:?}"), @@ -6746,6 +6784,44 @@ mod tests { assert!(result.is_err()); } + #[test] + fn sandbox_create_parses_bearer_passthrough_and_requires_expose() { + let cli = Cli::try_parse_from([ + "openshell", + "sandbox", + "create", + "--expose", + "4500", + "--expose-authorization-mode", + "bearer-passthrough", + ]) + .expect("create-time authorization mode should parse with --expose"); + match cli.command { + Some(Commands::Sandbox { + command: + Some(SandboxCommands::Create { + expose_authorization_mode, + .. + }), + }) => assert_eq!( + expose_authorization_mode, + CliServiceAuthorizationMode::BearerPassthrough + ), + other => panic!("expected SandboxCommands::Create, got: {other:?}"), + } + + assert!( + Cli::try_parse_from([ + "openshell", + "sandbox", + "create", + "--expose-authorization-mode", + "bearer-passthrough", + ]) + .is_err() + ); + } + #[test] fn service_expose_accepts_positional_target_port_and_service() { let cli = Cli::try_parse_from([ @@ -6765,11 +6841,13 @@ mod tests { sandbox, target_port, service, + authorization_mode, }), }) => { assert_eq!(sandbox, "my-sandbox"); assert_eq!(target_port, 8080); assert_eq!(service.as_deref(), Some("api")); + assert_eq!(authorization_mode, CliServiceAuthorizationMode::Strip); } other => panic!("expected service expose command, got: {other:?}"), } @@ -6787,16 +6865,45 @@ mod tests { sandbox, target_port, service, + authorization_mode, }), }) => { assert_eq!(sandbox, "my-sandbox"); assert_eq!(target_port, 8080); assert_eq!(service, None); + assert_eq!(authorization_mode, CliServiceAuthorizationMode::Strip); } other => panic!("expected service expose command, got: {other:?}"), } } + #[test] + fn service_expose_parses_bearer_passthrough() { + let cli = Cli::try_parse_from([ + "openshell", + "service", + "expose", + "my-sandbox", + "4500", + "--authorization-mode", + "bearer-passthrough", + ]) + .expect("service authorization mode should parse"); + + match cli.command { + Some(Commands::Service { + command: + Some(ServiceCommands::Expose { + authorization_mode, .. + }), + }) => assert_eq!( + authorization_mode, + CliServiceAuthorizationMode::BearerPassthrough + ), + other => panic!("expected service expose command, got: {other:?}"), + } + } + #[test] fn service_alias_parses_service_commands() { let cli = Cli::try_parse_from(["openshell", "svc", "expose", "my-sandbox", "8080"]) @@ -6809,6 +6916,7 @@ mod tests { sandbox, target_port, service, + .. }), }) => { assert_eq!(sandbox, "my-sandbox"); diff --git a/crates/openshell-cli/src/run.rs b/crates/openshell-cli/src/run.rs index ce63bc9f7b..02c806c421 100644 --- a/crates/openshell-cli/src/run.rs +++ b/crates/openshell-cli/src/run.rs @@ -58,9 +58,9 @@ use openshell_core::proto::{ RevokeSshSessionRequest, Sandbox, SandboxCondition, SandboxPhase, SandboxPolicy, SandboxResources, SandboxRestartPolicy, SandboxServiceExposure, SandboxServiceLevel, SandboxSpec, SandboxStartup, SandboxTemplate, SandboxWorkloadConfig, SandboxWorkloadTemplate, - SandboxWorkloadTemplateSpec, ServiceEndpointResponse, SettingScope, StartSandboxRequest, - StopSandboxRequest, TcpForwardFrame, TcpForwardInit, TcpRelayTarget, UpdateConfigRequest, - WatchSandboxRequest, exec_sandbox_event, tcp_forward_init, + SandboxWorkloadTemplateSpec, ServiceAuthorizationMode, ServiceEndpointResponse, SettingScope, + StartSandboxRequest, StopSandboxRequest, TcpForwardFrame, TcpForwardInit, TcpRelayTarget, + UpdateConfigRequest, WatchSandboxRequest, exec_sandbox_event, tcp_forward_init, }; use openshell_core::settings; use openshell_core::{ObjectId, ObjectName, ObjectWorkspace}; @@ -443,6 +443,7 @@ pub struct SandboxCreateConfig<'a> { pub policy: Option<&'a str>, pub forward: Option, pub expose: Option, + pub expose_authorization_mode: ServiceAuthorizationMode, pub command: &'a [String], pub tty_override: Option, pub auto_providers_override: Option, @@ -472,6 +473,7 @@ impl Default for SandboxCreateConfig<'_> { policy: None, forward: None, expose: None, + expose_authorization_mode: ServiceAuthorizationMode::Strip, command: &[], tty_override: None, auto_providers_override: None, @@ -509,6 +511,7 @@ pub async fn sandbox_create( policy, forward, expose, + expose_authorization_mode, command, tty_override, auto_providers_override, @@ -693,6 +696,7 @@ pub async fn sandbox_create( .map(|target_port| SandboxServiceExposure { service: String::new(), target_port: u32::from(target_port), + authorization_mode: expose_authorization_mode as i32, }) .into_iter() .collect(), @@ -3823,11 +3827,20 @@ pub async fn service_expose( sandbox: &str, service: &str, target_port: u16, + authorization_mode: ServiceAuthorizationMode, workspace: &str, tls: &TlsOptions, ) -> Result<()> { - let response = - expose_service_endpoint(server, sandbox, service, target_port, workspace, tls).await?; + let response = expose_service_endpoint( + server, + sandbox, + service, + target_port, + authorization_mode, + workspace, + tls, + ) + .await?; if service.is_empty() { println!( @@ -3857,6 +3870,7 @@ async fn expose_service_endpoint( sandbox: &str, service: &str, target_port: u16, + authorization_mode: ServiceAuthorizationMode, workspace: &str, tls: &TlsOptions, ) -> Result { @@ -3868,6 +3882,7 @@ async fn expose_service_endpoint( name: service.to_string(), target_port: u32::from(target_port), domain: true, + authorization_mode: authorization_mode as i32, workspace_scope: Some(openshell_core::proto::workspace_selector( workspace.to_string(), )), @@ -4040,6 +4055,7 @@ fn print_service_endpoint_table( .map_or("", |m| m.workspace.as_str()); let service = service_display_name(&endpoint.name).to_string(); let target = format!("127.0.0.1:{}", endpoint.target_port); + let authorization = service_authorization_mode_name(endpoint.authorization_mode); let url = if response.url.is_empty() { String::new() } else { @@ -4050,6 +4066,7 @@ fn print_service_endpoint_table( endpoint.sandbox.clone(), service, target, + authorization, url, )) }) @@ -4061,7 +4078,7 @@ fn print_service_endpoint_table( let ws_width = if all_workspaces { rows.iter() - .map(|(ws, _, _, _, _)| ws.len()) + .map(|(ws, _, _, _, _, _)| ws.len()) .max() .unwrap_or(9) .max(9) @@ -4070,50 +4087,52 @@ fn print_service_endpoint_table( }; let sandbox_width = rows .iter() - .map(|(_, sandbox, _, _, _)| sandbox.len()) + .map(|(_, sandbox, _, _, _, _)| sandbox.len()) .max() .unwrap_or(7) .max(7); let service_width = rows .iter() - .map(|(_, _, service, _, _)| service.len()) + .map(|(_, _, service, _, _, _)| service.len()) .max() .unwrap_or(7) .max(7); let target_width = rows .iter() - .map(|(_, _, _, target, _)| target.len()) + .map(|(_, _, _, target, _, _)| target.len()) .max() .unwrap_or(6) .max(6); if all_workspaces { println!( - "{: &str { if service.is_empty() { "-" } else { service } } +fn service_authorization_mode_name(mode: i32) -> &'static str { + match ServiceAuthorizationMode::try_from(mode).unwrap_or(ServiceAuthorizationMode::Strip) { + ServiceAuthorizationMode::BearerPassthrough => "bearer_passthrough", + ServiceAuthorizationMode::Unspecified | ServiceAuthorizationMode::Strip => "strip", + } +} + /// Read gcloud Application Default Credentials from disk. /// /// Returns `(client_id, client_secret, refresh_token)`. @@ -6521,8 +6548,9 @@ mod tests { PolicySource, PolicyStatus, ResourceRequirements, Sandbox, SandboxCondition, SandboxPhase, SandboxPolicy, SandboxPolicyRevision, SandboxResources, SandboxRestartPolicy, SandboxSpec, SandboxStatus, SandboxWorkloadConfig, SandboxWorkloadTemplate, - SandboxWorkloadTemplateProvenance, SandboxWorkloadTemplateSpec, ServiceEndpoint, - ServiceEndpointResponse, WorkspaceMember, WorkspaceRole, datamodel::v1::ObjectMeta, + SandboxWorkloadTemplateProvenance, SandboxWorkloadTemplateSpec, ServiceAuthorizationMode, + ServiceEndpoint, ServiceEndpointResponse, WorkspaceMember, WorkspaceRole, + datamodel::v1::ObjectMeta, }; #[test] @@ -6639,6 +6667,7 @@ mod tests { sandbox: "api".to_string(), name: String::new(), target_port: 8080, + authorization_mode: ServiceAuthorizationMode::BearerPassthrough as i32, ..Default::default() }), url: "https://api.openshell.localhost:3000/".to_string(), @@ -6653,6 +6682,7 @@ mod tests { "sandbox": "api", "service": "", "target_port": 8080, + "authorization_mode": "bearer_passthrough", "url": "https://api.openshell.localhost:17670/", }) ); diff --git a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs index 0b638d5694..964559a8ad 100644 --- a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs +++ b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs @@ -2784,6 +2784,8 @@ async fn sandbox_create_exposes_service_after_ready_and_keeps_sandbox() { name: Some("sandbox"), keep: false, expose: Some(4500), + expose_authorization_mode: + openshell_core::proto::ServiceAuthorizationMode::BearerPassthrough, detach: true, ..test_config() }, @@ -2799,9 +2801,38 @@ async fn sandbox_create_exposes_service_after_ready_and_keeps_sandbox() { assert_eq!(create_requests[0].service_exposures.len(), 1); assert_eq!(create_requests[0].service_exposures[0].service, ""); assert_eq!(create_requests[0].service_exposures[0].target_port, 4500); + assert_eq!( + create_requests[0].service_exposures[0].authorization_mode(), + openshell_core::proto::ServiceAuthorizationMode::BearerPassthrough + ); assert!(expose_service_requests(&server).await.is_empty()); } +#[tokio::test] +async fn service_expose_forwards_bearer_passthrough_mode() { + let server = run_server().await; + let tls = test_tls(&server); + + run::service_expose( + &server.endpoint, + "sandbox", + "codex", + 4500, + openshell_core::proto::ServiceAuthorizationMode::BearerPassthrough, + "default", + &tls, + ) + .await + .expect("service expose should succeed"); + + let requests = expose_service_requests(&server).await; + assert_eq!(requests.len(), 1); + assert_eq!( + requests[0].authorization_mode(), + openshell_core::proto::ServiceAuthorizationMode::BearerPassthrough + ); +} + #[tokio::test] async fn sandbox_forward_background_tracks_owned_child_when_pid_discovery_fails() { let server = run_server().await; diff --git a/crates/openshell-sdk/README.md b/crates/openshell-sdk/README.md index 4f721cbece..68c95e0c5b 100644 --- a/crates/openshell-sdk/README.md +++ b/crates/openshell-sdk/README.md @@ -56,8 +56,10 @@ portable workload shape and driver config. Failures map to a typed `SdkError` with a discriminable kind. Set `SandboxSpec::service_exposures` to register named or unnamed loopback HTTP -services during creation. Each `ServiceExposure` contains a service name and a -target port; an empty name selects the unnamed endpoint. The returned +services during creation. Each `ServiceExposure` contains a service name, a +target port, and an authorization mode; an empty name selects the unnamed +endpoint. Authorization is stripped by default. Select `BearerPassthrough` only +when the sandbox application validates its own bearer credential. The returned `SandboxRef::service_urls` map contains each routed URL under the same name. Curated calls without a workspace argument explicitly select the `default` diff --git a/crates/openshell-sdk/src/client.rs b/crates/openshell-sdk/src/client.rs index 081ce94f89..69b75ffbd2 100644 --- a/crates/openshell-sdk/src/client.rs +++ b/crates/openshell-sdk/src/client.rs @@ -1359,6 +1359,9 @@ fn create_sandbox_request(spec: SandboxSpec) -> proto::CreateSandboxRequest { .map(|exposure| proto::SandboxServiceExposure { service: exposure.service, target_port: u32::from(exposure.target_port), + authorization_mode: proto::ServiceAuthorizationMode::from( + exposure.authorization_mode, + ) as i32, }) .collect(), } @@ -1397,6 +1400,9 @@ fn create_sandbox_from_template_request( .map(|exposure| proto::SandboxServiceExposure { service: exposure.service, target_port: u32::from(exposure.target_port), + authorization_mode: proto::ServiceAuthorizationMode::from( + exposure.authorization_mode, + ) as i32, }) .collect(), } diff --git a/crates/openshell-sdk/src/lib.rs b/crates/openshell-sdk/src/lib.rs index 2d79b28cc6..5459fa7532 100644 --- a/crates/openshell-sdk/src/lib.rs +++ b/crates/openshell-sdk/src/lib.rs @@ -54,6 +54,6 @@ pub use types::{ LogLine, PlatformEvent, SandboxPhase, SandboxRef, SandboxResources, SandboxRestartPolicy, SandboxServiceLevel, SandboxSpec, SandboxStartup, SandboxTemplateCreateSpec, SandboxTemplateListOptions, SandboxWorkloadConfig, SandboxWorkloadTemplate, - SandboxWorkloadTemplateProvenance, SandboxWorkloadTemplateSpec, ServiceExposure, ServiceStatus, - WatchEvent, WatchOptions, WorkspaceRef, + SandboxWorkloadTemplateProvenance, SandboxWorkloadTemplateSpec, ServiceAuthorizationMode, + ServiceExposure, ServiceStatus, WatchEvent, WatchOptions, WorkspaceRef, }; diff --git a/crates/openshell-sdk/src/types.rs b/crates/openshell-sdk/src/types.rs index b7297a3fee..aa2bc71bf9 100644 --- a/crates/openshell-sdk/src/types.rs +++ b/crates/openshell-sdk/src/types.rs @@ -296,6 +296,27 @@ pub struct ServiceExposure { pub service: String, /// Loopback TCP port inside the sandbox. pub target_port: u16, + /// Whether the gateway strips or forwards an application bearer credential. + pub authorization_mode: ServiceAuthorizationMode, +} + +/// Handling for an incoming application `Authorization` header. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum ServiceAuthorizationMode { + /// Remove the header before proxying to the sandbox service. + #[default] + Strip, + /// Forward one syntactically valid bearer credential unchanged. + BearerPassthrough, +} + +impl From for proto::ServiceAuthorizationMode { + fn from(value: ServiceAuthorizationMode) -> Self { + match value { + ServiceAuthorizationMode::Strip => Self::Strip, + ServiceAuthorizationMode::BearerPassthrough => Self::BearerPassthrough, + } + } } /// Caller intent for creating a sandbox from a named workload template. diff --git a/crates/openshell-sdk/tests/client_mock.rs b/crates/openshell-sdk/tests/client_mock.rs index 1c86ada7dd..9506160f07 100644 --- a/crates/openshell-sdk/tests/client_mock.rs +++ b/crates/openshell-sdk/tests/client_mock.rs @@ -13,8 +13,8 @@ use openshell_core::proto::open_shell_server::{OpenShell, OpenShellServer}; use openshell_sdk::{ AuthConfig, ClientConfig, ExecOptions, ListOptions, OpenShellClient, Refresh, RefreshError, RefreshedToken, SandboxPhase, SandboxSpec, SandboxTemplateCreateSpec, - SandboxTemplateListOptions, ServiceExposure, ServiceStatus as SdkServiceStatus, WatchEvent, - WatchOptions, + SandboxTemplateListOptions, ServiceAuthorizationMode, ServiceExposure, + ServiceStatus as SdkServiceStatus, WatchEvent, WatchOptions, }; use std::collections::HashMap; use std::sync::Arc; @@ -1139,6 +1139,7 @@ async fn create_sandbox_passes_spec_through() { service_exposures: vec![ServiceExposure { service: "web".to_string(), target_port: 8080, + authorization_mode: ServiceAuthorizationMode::BearerPassthrough, }], ..Default::default() }; @@ -1158,6 +1159,10 @@ async fn create_sandbox_passes_spec_through() { assert_eq!(observed.service_exposures.len(), 1); assert_eq!(observed.service_exposures[0].service, "web"); assert_eq!(observed.service_exposures[0].target_port, 8080); + assert_eq!( + observed.service_exposures[0].authorization_mode(), + proto::ServiceAuthorizationMode::BearerPassthrough + ); let observed_spec = observed.spec.unwrap(); assert!( observed_spec diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index da0ebffef0..d2e9642263 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -25,6 +25,8 @@ use hyper_util::rt::TokioIo; use openshell_core::extension_protocol::{ ExtensionFamily, NegotiatedExtension, gateway_metadata, negotiate, }; +#[cfg(test)] +use openshell_core::proto::ServiceAuthorizationMode; use openshell_core::proto::compute::v1::{ AuthenticateSandboxRequest, CreateSandboxRequest, DeleteSandboxRequest, DeleteWorkspaceRequest, DeleteWorkspaceResponse, DriverCondition, DriverPlatformEvent, DriverResourceRequirements, @@ -9509,6 +9511,7 @@ mod tests { name: "web".to_string(), target_port: 8080, domain: true, + authorization_mode: ServiceAuthorizationMode::Strip as i32, } } diff --git a/crates/openshell-server/src/grpc/mutation_replay/tests.rs b/crates/openshell-server/src/grpc/mutation_replay/tests.rs index 28dfc688f2..7b784213b6 100644 --- a/crates/openshell-server/src/grpc/mutation_replay/tests.rs +++ b/crates/openshell-server/src/grpc/mutation_replay/tests.rs @@ -7,7 +7,8 @@ use openshell_core::proto::datamodel::v1::ObjectMeta; use openshell_core::proto::open_shell_server::OpenShell; use openshell_core::proto::{ CreateSandboxRequest, SandboxServiceExposure, SandboxSpec, SandboxWorkloadConfig, - SandboxWorkloadTemplate, SandboxWorkloadTemplateSpec, WorkspaceMember, WorkspaceRole, + SandboxWorkloadTemplate, SandboxWorkloadTemplateSpec, ServiceAuthorizationMode, + WorkspaceMember, WorkspaceRole, }; use openshell_core::rpc_error::StatusExt; use std::collections::HashMap; @@ -113,6 +114,7 @@ async fn create_sandbox_replay_preserves_service_urls() { service_exposures: vec![SandboxServiceExposure { service: "web".into(), target_port: 8080, + authorization_mode: ServiceAuthorizationMode::Strip as i32, }], request_id: uuid::Uuid::new_v4().to_string(), ..Default::default() @@ -124,13 +126,26 @@ async fn create_sandbox_replay_preserves_service_urls() { .unwrap() .into_inner(); let replay = service - .create_sandbox(authed_request(request)) + .create_sandbox(authed_request(request.clone())) .await .unwrap(); assert_eq!(replay.metadata().get("openshell-replayed").unwrap(), "true"); assert_eq!(replay.get_ref().service_urls, original.service_urls); assert_eq!(replay.into_inner(), original); + + let mut changed_authorization = request; + changed_authorization.service_exposures[0].authorization_mode = + ServiceAuthorizationMode::BearerPassthrough as i32; + assert_eq!( + reason( + &service + .create_sandbox(authed_request(changed_authorization)) + .await + .unwrap_err() + ), + "REQUEST_ID_PAYLOAD_MISMATCH" + ); } async fn exercise_backend(url: &str) { diff --git a/crates/openshell-server/src/grpc/sandbox.rs b/crates/openshell-server/src/grpc/sandbox.rs index 8e3aa58372..074b2590e0 100644 --- a/crates/openshell-server/src/grpc/sandbox.rs +++ b/crates/openshell-server/src/grpc/sandbox.rs @@ -680,6 +680,11 @@ async fn handle_create_sandbox_inner( &sandbox, &exposure.service, exposure.target_port, + super::service::validate_service_exposure_request( + &exposure.service, + exposure.target_port, + exposure.authorization_mode, + )?, ) .await { @@ -741,7 +746,11 @@ fn validate_create_sandbox_request_pre_io( } let mut service_names = HashSet::with_capacity(request.service_exposures.len()); for exposure in &request.service_exposures { - super::service::validate_service_exposure_request(&exposure.service, exposure.target_port)?; + super::service::validate_service_exposure_request( + &exposure.service, + exposure.target_port, + exposure.authorization_mode, + )?; if !service_names.insert(exposure.service.as_str()) { return Err(Status::invalid_argument(format!( "duplicate service exposure name: '{}'", @@ -3924,7 +3933,9 @@ mod tests { test_server_state_with_driver, }; use openshell_core::proto::datamodel::v1::ObjectMeta; - use openshell_core::proto::{GpuResourceRequirements, SandboxServiceExposure, ServiceEndpoint}; + use openshell_core::proto::{ + GpuResourceRequirements, SandboxServiceExposure, ServiceAuthorizationMode, ServiceEndpoint, + }; // ---- shell_escape ---- @@ -6720,10 +6731,12 @@ mod tests { SandboxServiceExposure { service: String::new(), target_port: 4500, + authorization_mode: ServiceAuthorizationMode::Unspecified as i32, }, SandboxServiceExposure { service: "metrics".to_string(), target_port: 9090, + authorization_mode: ServiceAuthorizationMode::BearerPassthrough as i32, }, ], ..Default::default() @@ -6756,9 +6769,46 @@ mod tests { assert_eq!(endpoint.name, service); assert_eq!(endpoint.target_port, target_port); assert!(endpoint.domain); + let expected_mode = if service.is_empty() { + ServiceAuthorizationMode::Strip + } else { + ServiceAuthorizationMode::BearerPassthrough + }; + assert_eq!(endpoint.authorization_mode(), expected_mode); } } + #[tokio::test] + async fn create_sandbox_rejects_unknown_service_authorization_mode_before_persisting() { + let state = test_server_state().await; + let error = handle_create_sandbox( + &state, + authed_request(CreateSandboxRequest { + name: "invalid-service-authorization".to_string(), + spec: Some(SandboxSpec::default()), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + service_exposures: vec![SandboxServiceExposure { + service: String::new(), + target_port: 4500, + authorization_mode: 99, + }], + ..Default::default() + }), + ) + .await + .expect_err("unknown service authorization mode should be rejected"); + + assert_eq!(error.code(), tonic::Code::InvalidArgument); + assert!( + state + .store + .get_message_by_name::("default", "invalid-service-authorization") + .await + .expect("sandbox lookup should succeed") + .is_none() + ); + } + #[tokio::test] async fn create_sandbox_begins_rollback_when_service_exposure_fails() { let state = test_server_state().await; @@ -6788,10 +6838,12 @@ mod tests { SandboxServiceExposure { service: "web".to_string(), target_port: 8080, + authorization_mode: ServiceAuthorizationMode::Strip as i32, }, SandboxServiceExposure { service: "metrics".to_string(), target_port: 9090, + authorization_mode: ServiceAuthorizationMode::Strip as i32, }, ], ..Default::default() @@ -6875,10 +6927,12 @@ mod tests { SandboxServiceExposure { service: "web".to_string(), target_port: 8080, + authorization_mode: ServiceAuthorizationMode::Strip as i32, }, SandboxServiceExposure { service: "web".to_string(), target_port: 8081, + authorization_mode: ServiceAuthorizationMode::Strip as i32, }, ], ..Default::default() diff --git a/crates/openshell-server/src/grpc/service.rs b/crates/openshell-server/src/grpc/service.rs index 675b695656..bb44f395b8 100644 --- a/crates/openshell-server/src/grpc/service.rs +++ b/crates/openshell-server/src/grpc/service.rs @@ -7,7 +7,8 @@ use std::sync::Arc; use openshell_core::proto::datamodel::v1::ObjectMeta; use openshell_core::proto::{ DeleteServiceRequest, DeleteServiceResponse, ExposeServiceRequest, GetServiceRequest, - ListServicesRequest, ListServicesResponse, Sandbox, ServiceEndpoint, ServiceEndpointResponse, + ListServicesRequest, ListServicesResponse, Sandbox, ServiceAuthorizationMode, ServiceEndpoint, + ServiceEndpointResponse, }; use openshell_core::{GetResourceVersion, ObjectId, ObjectName, ObjectWorkspace}; use prost::Message as _; @@ -49,19 +50,37 @@ pub(super) async fn handle_expose_service( super::workspace::resolve_workspace(state.store.as_ref(), sandbox.object_workspace()) .await? .ensure_active()?; - validate_service_exposure_request(&req.name, req.target_port)?; - expose_service_endpoint(state, &workspace, &sandbox, &req.name, req.target_port).await + let authorization_mode = + validate_service_exposure_request(&req.name, req.target_port, req.authorization_mode)?; + expose_service_endpoint( + state, + &workspace, + &sandbox, + &req.name, + req.target_port, + authorization_mode, + ) + .await } pub(super) fn validate_service_exposure_request( service: &str, target_port: u32, -) -> Result<(), Status> { + authorization_mode: i32, +) -> Result { validate_optional_endpoint_name("service", service, MAX_SERVICE_NAME_LEN)?; if target_port == 0 || target_port > u32::from(u16::MAX) { return Err(Status::invalid_argument("target_port must be in 1..=65535")); } - Ok(()) + match ServiceAuthorizationMode::try_from(authorization_mode) { + Ok(ServiceAuthorizationMode::Unspecified | ServiceAuthorizationMode::Strip) => { + Ok(ServiceAuthorizationMode::Strip) + } + Ok(ServiceAuthorizationMode::BearerPassthrough) => { + Ok(ServiceAuthorizationMode::BearerPassthrough) + } + Err(_) => Err(Status::invalid_argument("authorization_mode is invalid")), + } } pub(super) async fn expose_service_endpoint( @@ -70,6 +89,7 @@ pub(super) async fn expose_service_endpoint( sandbox: &Sandbox, service: &str, target_port: u32, + authorization_mode: ServiceAuthorizationMode, ) -> Result, Status> { let sandbox_name = sandbox.object_name(); @@ -132,6 +152,7 @@ pub(super) async fn expose_service_endpoint( name: service.to_string(), target_port, domain: true, + authorization_mode: authorization_mode as i32, }; // Single-attempt CAS write: fails with ABORTED on concurrent modification @@ -344,8 +365,16 @@ async fn get_service_endpoint( fn service_endpoint_response( state: &Arc, - endpoint: ServiceEndpoint, + mut endpoint: ServiceEndpoint, ) -> ServiceEndpointResponse { + endpoint.authorization_mode = + match ServiceAuthorizationMode::try_from(endpoint.authorization_mode) { + Ok(ServiceAuthorizationMode::BearerPassthrough) => { + ServiceAuthorizationMode::BearerPassthrough as i32 + } + Ok(ServiceAuthorizationMode::Unspecified | ServiceAuthorizationMode::Strip) + | Err(_) => ServiceAuthorizationMode::Strip as i32, + }; let workspace = endpoint.object_workspace(); let url = service_routing::endpoint_url(&state.config, workspace, &endpoint.sandbox, &endpoint.name) @@ -456,6 +485,100 @@ mod tests { assert!(validate_endpoint_name("service", "Web", 28).is_err()); } + #[test] + fn authorization_mode_defaults_to_strip_and_rejects_unknown_values() { + assert_eq!( + validate_service_exposure_request("web", 8080, 0).unwrap(), + ServiceAuthorizationMode::Strip + ); + assert_eq!( + validate_service_exposure_request( + "web", + 8080, + ServiceAuthorizationMode::BearerPassthrough as i32, + ) + .unwrap(), + ServiceAuthorizationMode::BearerPassthrough + ); + assert_eq!( + validate_service_exposure_request("web", 8080, 99) + .unwrap_err() + .code(), + tonic::Code::InvalidArgument + ); + } + + #[tokio::test] + async fn unknown_authorization_mode_is_rejected_before_persistence() { + let state = test_server_state().await; + seed_sandbox(&state, "my-sandbox").await; + + let error = handle_expose_service( + &state, + authed_request(ExposeServiceRequest { + sandbox: "my-sandbox".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + name: "web".to_string(), + target_port: 8080, + authorization_mode: 99, + ..Default::default() + }), + ) + .await + .unwrap_err(); + + assert_eq!(error.code(), tonic::Code::InvalidArgument); + assert!( + get_service_endpoint(&state, "default", "my-sandbox", "web") + .await + .unwrap() + .is_none() + ); + } + + #[tokio::test] + async fn legacy_unspecified_authorization_mode_is_reported_as_strip() { + let state = test_server_state().await; + seed_sandbox(&state, "my-sandbox").await; + + handle_expose_service( + &state, + authed_request(ExposeServiceRequest { + sandbox: "my-sandbox".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + name: "web".to_string(), + target_port: 8080, + ..Default::default() + }), + ) + .await + .unwrap(); + + let mut stored = get_service_endpoint(&state, "default", "my-sandbox", "web") + .await + .unwrap() + .unwrap(); + stored.authorization_mode = ServiceAuthorizationMode::Unspecified as i32; + state.store.put_message(&stored).await.unwrap(); + + let response = handle_get_service( + &state, + authed_request(GetServiceRequest { + sandbox: "my-sandbox".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + name: "web".to_string(), + }), + ) + .await + .unwrap() + .into_inner(); + + assert_eq!( + response.endpoint.unwrap().authorization_mode(), + ServiceAuthorizationMode::Strip + ); + } + #[tokio::test] async fn endpoint_lifecycle_round_trip() { let state = test_server_state().await; @@ -472,12 +595,17 @@ mod tests { name: "web".to_string(), target_port: 8080, domain: true, + authorization_mode: ServiceAuthorizationMode::Unspecified as i32, }), ) .await .unwrap() .into_inner(); assert_eq!(exposed.endpoint.as_ref().unwrap().target_port, 8080); + assert_eq!( + exposed.endpoint.as_ref().unwrap().authorization_mode(), + ServiceAuthorizationMode::Strip + ); let listed = handle_list_services( &state, @@ -511,6 +639,26 @@ mod tests { .into_inner(); assert_eq!(fetched.endpoint.as_ref().unwrap().target_port, 8080); + let updated = handle_expose_service( + &state, + authed_request(ExposeServiceRequest { + sandbox: "my-sandbox".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + name: "web".to_string(), + target_port: 9090, + authorization_mode: ServiceAuthorizationMode::BearerPassthrough as i32, + ..Default::default() + }), + ) + .await + .unwrap() + .into_inner(); + assert_eq!(updated.endpoint.as_ref().unwrap().target_port, 9090); + assert_eq!( + updated.endpoint.as_ref().unwrap().authorization_mode(), + ServiceAuthorizationMode::BearerPassthrough + ); + let deleted = handle_delete_service( &state, authed_request(DeleteServiceRequest { @@ -643,6 +791,7 @@ mod tests { name: "web".to_string(), target_port: 8080, domain: true, + authorization_mode: ServiceAuthorizationMode::Strip as i32, }), ) .await @@ -661,6 +810,7 @@ mod tests { name: "web".to_string(), target_port: 9090, domain: true, + authorization_mode: ServiceAuthorizationMode::Strip as i32, }), ) .await @@ -713,6 +863,7 @@ mod tests { name: "web".to_string(), target_port: 7070, domain: true, + authorization_mode: ServiceAuthorizationMode::Strip as i32, }), ) .await @@ -732,6 +883,7 @@ mod tests { name: "web".to_string(), target_port: 8080, domain: true, + authorization_mode: ServiceAuthorizationMode::Strip as i32, }), ) .await @@ -750,6 +902,7 @@ mod tests { name: "web".to_string(), target_port: 9090, domain: true, + authorization_mode: ServiceAuthorizationMode::Strip as i32, }), ) .await @@ -840,6 +993,7 @@ mod tests { name: "web".to_string(), target_port: 8080, domain: true, + authorization_mode: ServiceAuthorizationMode::Strip as i32, }), ) .await @@ -856,6 +1010,7 @@ mod tests { name: "web".to_string(), target_port: 9090, domain: true, + authorization_mode: ServiceAuthorizationMode::Strip as i32, }), ) .await @@ -999,6 +1154,7 @@ mod tests { name: "api".to_string(), target_port: 3000, domain: true, + authorization_mode: ServiceAuthorizationMode::Strip as i32, }), ) .await diff --git a/crates/openshell-server/src/service_routing.rs b/crates/openshell-server/src/service_routing.rs index 247363a05f..c139d282ab 100644 --- a/crates/openshell-server/src/service_routing.rs +++ b/crates/openshell-server/src/service_routing.rs @@ -11,7 +11,9 @@ use http::{HeaderMap, HeaderValue, Method, Request, Response, StatusCode, header use hyper_util::rt::TokioIo; use openshell_core::ObjectId; use openshell_core::config::ServiceRoutingConfig; -use openshell_core::proto::{Sandbox, SandboxPhase, ServiceEndpoint, TcpRelayTarget, relay_open}; +use openshell_core::proto::{ + Sandbox, SandboxPhase, ServiceAuthorizationMode, ServiceEndpoint, TcpRelayTarget, relay_open, +}; use openshell_ocsf::{ ActionId, ActivityId, ConfigStateChangeBuilder, DispositionId, Endpoint, EventContext, HttpActivityBuilder, HttpRequest, HttpResponse as OcsfHttpResponse, NetworkActivityBuilder, @@ -428,6 +430,19 @@ async fn proxy_to_endpoint( ); return Err(err); } + let authorization_mode = effective_authorization_mode(endpoint.authorization_mode); + if validate_application_authorization(&req, authorization_mode).is_err() { + let err = ServiceRouteError::invalid_request(); + emit_service_http_failure( + &state, + &req, + &sandbox_name, + &service_name, + Some(&endpoint), + &err, + ); + return Err(err); + } let websocket_upgrade = is_websocket_upgrade(&req); let downstream_upgrade = websocket_upgrade.then(|| hyper::upgrade::on(&mut req)); @@ -446,7 +461,7 @@ async fn proxy_to_endpoint( None => open_upstream(&state, &sandbox, &endpoint, target_port, websocket_upgrade).await?, }; - let upstream = build_upstream_request(req, target_port, websocket_upgrade)?; + let upstream = build_upstream_request(req, target_port, websocket_upgrade, authorization_mode)?; let replay = reused.then(|| replayable_request(&upstream)).flatten(); let first_attempt = if reused { sender.try_send_request(upstream).await.map_err(|mut err| { @@ -626,6 +641,7 @@ fn build_upstream_request( req: Request, target_port: u16, preserve_upgrade_headers: bool, + authorization_mode: ServiceAuthorizationMode, ) -> Result, ServiceRouteError> { let (parts, body) = req.into_parts(); let path = parts.uri.path_and_query().map_or("/", |path| path.as_str()); @@ -644,7 +660,7 @@ fn build_upstream_request( for (name, value) in &parts.headers { if (is_hop_by_hop_header(name) && !(preserve_upgrade_headers && is_websocket_hop_by_hop_header(name))) - || is_gateway_auth_header(name) + || is_gateway_auth_header(name, authorization_mode) { continue; } @@ -724,15 +740,83 @@ fn is_websocket_hop_by_hop_header(name: &header::HeaderName) -> bool { matches!(name.as_str(), "connection" | "upgrade") } -fn is_gateway_auth_header(name: &header::HeaderName) -> bool { - matches!( - name.as_str(), - "authorization" - | "cf-access-jwt-assertion" - | "x-forwarded-client-cert" - | "x-ssl-client-cert" - | "x-client-cert" - ) +pub fn effective_authorization_mode(value: i32) -> ServiceAuthorizationMode { + match ServiceAuthorizationMode::try_from(value) { + Ok(ServiceAuthorizationMode::BearerPassthrough) => { + ServiceAuthorizationMode::BearerPassthrough + } + Ok(ServiceAuthorizationMode::Unspecified | ServiceAuthorizationMode::Strip) | Err(_) => { + ServiceAuthorizationMode::Strip + } + } +} + +fn authorization_mode_label(value: i32) -> &'static str { + match effective_authorization_mode(value) { + ServiceAuthorizationMode::BearerPassthrough => "bearer_passthrough", + ServiceAuthorizationMode::Unspecified | ServiceAuthorizationMode::Strip => "strip", + } +} + +fn validate_application_authorization( + req: &Request, + authorization_mode: ServiceAuthorizationMode, +) -> Result<(), ServiceRouteError> { + if authorization_mode != ServiceAuthorizationMode::BearerPassthrough { + return Ok(()); + } + + let mut values = req.headers().get_all(header::AUTHORIZATION).iter(); + let Some(value) = values.next() else { + return Ok(()); + }; + if values.next().is_some() { + return Err(ServiceRouteError::invalid_request()); + } + + let value = value + .to_str() + .map_err(|_| ServiceRouteError::invalid_request())?; + let Some((scheme, credential)) = value.split_once(' ') else { + return Err(ServiceRouteError::invalid_request()); + }; + let credential = credential.trim_start_matches(' '); + if !scheme.eq_ignore_ascii_case("bearer") || !is_bearer_token68(credential) { + return Err(ServiceRouteError::invalid_request()); + } + Ok(()) +} + +fn is_bearer_token68(value: &str) -> bool { + let mut saw_data = false; + let mut saw_padding = false; + for byte in value.bytes() { + if byte == b'=' { + saw_padding = true; + } else if !saw_padding + && (byte.is_ascii_alphanumeric() + || matches!(byte, b'-' | b'.' | b'_' | b'~' | b'+' | b'/')) + { + saw_data = true; + } else { + return false; + } + } + saw_data +} + +fn is_gateway_auth_header( + name: &header::HeaderName, + authorization_mode: ServiceAuthorizationMode, +) -> bool { + match name.as_str() { + "authorization" => authorization_mode != ServiceAuthorizationMode::BearerPassthrough, + "cf-access-jwt-assertion" + | "x-forwarded-client-cert" + | "x-ssl-client-cert" + | "x-client-cert" => true, + _ => false, + } } fn sanitize_cookie_header(value: &HeaderValue) -> Option { @@ -830,7 +914,11 @@ fn build_service_endpoint_config_event( )) .unmapped("endpoint_name", endpoint_name(endpoint)) .unmapped("service_name", endpoint.name.clone()) - .unmapped("target_port", u64::from(endpoint.target_port)); + .unmapped("target_port", u64::from(endpoint.target_port)) + .unmapped( + "authorization_mode", + authorization_mode_label(endpoint.authorization_mode), + ); if !url.is_empty() { builder = builder.unmapped("url", url.to_string()); @@ -849,6 +937,10 @@ fn build_service_endpoint_delete_event(endpoint: &ServiceEndpoint) -> OcsfEvent .unmapped("endpoint_name", endpoint_name(endpoint)) .unmapped("service_name", endpoint.name.clone()) .unmapped("target_port", u64::from(endpoint.target_port)) + .unmapped( + "authorization_mode", + authorization_mode_label(endpoint.authorization_mode), + ) .build() } @@ -990,6 +1082,7 @@ mod tests { name: "web".to_string(), target_port: 8080, domain: true, + authorization_mode: ServiceAuthorizationMode::Strip as i32, } } @@ -1233,6 +1326,7 @@ mod tests { assert_eq!(json["unmapped"]["endpoint_name"], "my-sandbox--web"); assert_eq!(json["unmapped"]["service_name"], "web"); assert_eq!(json["unmapped"]["target_port"], 8080); + assert_eq!(json["unmapped"]["authorization_mode"], "strip"); assert!( event .format_shorthand() @@ -1249,6 +1343,7 @@ mod tests { assert_eq!(json["unmapped"]["endpoint_name"], "my-sandbox--web"); assert_eq!(json["unmapped"]["service_name"], "web"); assert_eq!(json["unmapped"]["target_port"], 8080); + assert_eq!(json["unmapped"]["authorization_mode"], "strip"); assert!( event .format_shorthand() @@ -1309,7 +1404,8 @@ mod tests { .body(Body::empty()) .unwrap(); - let upstream = build_upstream_request(request, 8080, false).unwrap(); + let upstream = + build_upstream_request(request, 8080, false, ServiceAuthorizationMode::Strip).unwrap(); assert_eq!(upstream.uri(), "/path"); assert!(!upstream.headers().contains_key(header::AUTHORIZATION)); @@ -1322,6 +1418,145 @@ mod tests { assert_eq!(upstream.headers()["x-app-header"], "kept"); } + #[test] + fn unspecified_endpoint_authorization_mode_strips_authorization() { + let request = Request::builder() + .uri("/path") + .header(header::AUTHORIZATION, "Bearer application-token") + .body(Body::empty()) + .unwrap(); + + let mode = effective_authorization_mode(ServiceAuthorizationMode::Unspecified as i32); + validate_application_authorization(&request, mode).unwrap(); + let upstream = build_upstream_request(request, 8080, false, mode).unwrap(); + + assert_eq!(mode, ServiceAuthorizationMode::Strip); + assert!(!upstream.headers().contains_key(header::AUTHORIZATION)); + } + + #[test] + fn bearer_passthrough_preserves_valid_authorization_and_strips_gateway_identity() { + let request = Request::builder() + .uri("/path") + .header(header::AUTHORIZATION, "bEaReR application-token") + .header("cf-access-jwt-assertion", "edge-token") + .header("x-forwarded-client-cert", "cert") + .header(header::PROXY_AUTHORIZATION, "Basic proxy-secret") + .header( + header::COOKIE, + "theme=dark; CF_Authorization=edge-cookie; app=session", + ) + .body(Body::empty()) + .unwrap(); + + let mode = ServiceAuthorizationMode::BearerPassthrough; + validate_application_authorization(&request, mode).unwrap(); + let upstream = build_upstream_request(request, 8080, false, mode).unwrap(); + + assert_eq!( + upstream.headers()[header::AUTHORIZATION], + "bEaReR application-token" + ); + assert!(!upstream.headers().contains_key("cf-access-jwt-assertion")); + assert!(!upstream.headers().contains_key("x-forwarded-client-cert")); + assert!(!upstream.headers().contains_key(header::PROXY_AUTHORIZATION)); + assert_eq!( + upstream.headers()[header::COOKIE], + "theme=dark; app=session" + ); + } + + #[test] + fn bearer_passthrough_allows_missing_authorization() { + let request = Request::builder().uri("/path").body(Body::empty()).unwrap(); + + let mode = ServiceAuthorizationMode::BearerPassthrough; + validate_application_authorization(&request, mode).unwrap(); + let upstream = build_upstream_request(request, 8080, false, mode).unwrap(); + + assert!(!upstream.headers().contains_key(header::AUTHORIZATION)); + } + + #[test] + fn bearer_passthrough_rejects_ambiguous_or_malformed_authorization() { + for value in [ + "", + "Bearer", + "Bearer ", + "Basic abc", + "Bearer abc extra", + "Bearer abc,def", + "Bearer abc=def", + "Bearer\tabc", + " Bearer abc", + ] { + let request = Request::builder() + .uri("/path") + .header(header::AUTHORIZATION, value) + .body(Body::empty()) + .unwrap(); + assert!( + validate_application_authorization( + &request, + ServiceAuthorizationMode::BearerPassthrough, + ) + .is_err() + ); + } + + for value in ["Bearer abc", "bearer abc-._~+/==", "Bearer abc"] { + let request = Request::builder() + .uri("/path") + .header(header::AUTHORIZATION, value) + .body(Body::empty()) + .unwrap(); + validate_application_authorization( + &request, + ServiceAuthorizationMode::BearerPassthrough, + ) + .unwrap(); + } + + let mut request = Request::builder().uri("/path").body(Body::empty()).unwrap(); + request.headers_mut().append( + header::AUTHORIZATION, + HeaderValue::from_static("Bearer first"), + ); + request.headers_mut().append( + header::AUTHORIZATION, + HeaderValue::from_static("Bearer second"), + ); + assert!( + validate_application_authorization( + &request, + ServiceAuthorizationMode::BearerPassthrough, + ) + .is_err() + ); + } + + #[test] + fn authorization_value_is_not_in_service_routing_events() { + const SENTINEL: &str = "never-log-this-capability"; + let request = Request::builder() + .uri("/private") + .header(header::AUTHORIZATION, format!("Bearer {SENTINEL}")) + .body(Body::empty()) + .unwrap(); + let err = ServiceRouteError::invalid_request(); + let event = build_service_http_failure_event( + 18080, + &request, + "my-sandbox", + "web", + Some(&endpoint()), + &err, + ); + + assert!(!event.to_json().unwrap().to_string().contains(SENTINEL)); + assert!(!event.format_shorthand().contains(SENTINEL)); + } + #[test] fn detects_websocket_upgrade_request() { let request = Request::builder() @@ -1346,7 +1581,8 @@ mod tests { .body(Body::empty()) .unwrap(); - let upstream = build_upstream_request(request, 8080, true).unwrap(); + let upstream = + build_upstream_request(request, 8080, true, ServiceAuthorizationMode::Strip).unwrap(); assert_eq!(upstream.uri(), "/chat?session=main"); assert_eq!(upstream.headers()[header::CONNECTION], "Upgrade"); @@ -1355,6 +1591,30 @@ mod tests { assert_eq!(upstream.headers()[header::HOST], "127.0.0.1:8080"); } + #[test] + fn bearer_passthrough_preserves_authorization_on_websocket_upgrade() { + let request = Request::builder() + .method(Method::GET) + .uri("/chat") + .header(header::CONNECTION, "Upgrade") + .header(header::UPGRADE, "websocket") + .header("sec-websocket-key", "abc") + .header(header::AUTHORIZATION, "Bearer application-token") + .body(Body::empty()) + .unwrap(); + + let mode = ServiceAuthorizationMode::BearerPassthrough; + validate_application_authorization(&request, mode).unwrap(); + let upstream = build_upstream_request(request, 8080, true, mode).unwrap(); + + assert_eq!( + upstream.headers()[header::AUTHORIZATION], + "Bearer application-token" + ); + assert_eq!(upstream.headers()[header::CONNECTION], "Upgrade"); + assert_eq!(upstream.headers()[header::UPGRADE], "websocket"); + } + #[tokio::test] async fn load_endpoint_uses_workspace_for_lookup() { let store = crate::persistence::test_store().await; @@ -1375,6 +1635,7 @@ mod tests { name: "web".to_string(), target_port: 8080, domain: true, + authorization_mode: ServiceAuthorizationMode::Strip as i32, }; store.put_message(&ep).await.unwrap(); diff --git a/crates/openshell-server/src/storage_proto.rs b/crates/openshell-server/src/storage_proto.rs index 918ba14d9f..e6d8730aa0 100644 --- a/crates/openshell-server/src/storage_proto.rs +++ b/crates/openshell-server/src/storage_proto.rs @@ -126,15 +126,19 @@ mod tests { // inventories; the provider-environment file map is public-only. The // request has no provider-file capability field: older supervisors ignore // the additive file map while retaining the rest of the response. + // Service authorization also extends both schemas additively. Legacy + // payloads retain the safe Strip default. const PUBLIC_RPC_SCHEMA_SHA256: &str = - "2ed66dbc38c60eb96c7461c76d02813c177facfad93753b180534477270ad240"; + "2e156c6ad3c8eb51bcd30dc13b173fe339b38207a1b1f98f7be2e0cad8e3bd45"; const DURABLE_SCHEMA_SHA256: &str = - "399737f2a367d2e3a9d78cf84e2a97eef041835554599790788e4bbf318116c3"; + "38165d9d76f49fcfe98a12f241e032838a2376c1d1a87ea2796fd33b9b1a3541"; const PUBLIC_DURABLE_OVERLAP_SHA256: &str = - "d3c444ecdb42306af8a81791481fdfc147ddc54bf344e1c8e69bd06745c6cc3c"; + "761dea31a521b0650840fe2a823ad6e36a265ed323ba4506889781d630df0ee3"; // A persisted Sandbox without endpoint status retains its lifecycle fields; // the absent repeated field decodes empty and needs no database rewrite. const SANDBOX_WITHOUT_ENDPOINT_STATUS: &str = "0a1e0a0a73616e64626f782d6964120773616e64626f783a0764656661756c741a2b0a0773616e64626f782a0d0a05526561647912045472756530023807420d73757065727669736f722d6964"; + // ServiceEndpoint encoded before authorization_mode field 7 existed. + const SERVICE_ENDPOINT_WITHOUT_AUTHORIZATION_MODE: &str = "0a260a0b656e64706f696e742d6964120c73616e64626f782d2d77656228073a0764656661756c74120a73616e64626f782d69641a0773616e64626f78220377656228903f3001"; // Synthetic payloads generated with the public declarations at v0.0.116, // before their relocation into openshell.storage.v1. Values are deliberately // non-secret and the ordinary protobuf bytes contain no package names. @@ -594,9 +598,9 @@ mod tests { overlap_hash.as_str(), ), ( - (306, 26), - (93, 20), - (81, 20), + (306, 27), + (93, 21), + (81, 21), PUBLIC_RPC_SCHEMA_SHA256, DURABLE_SCHEMA_SHA256, PUBLIC_DURABLE_OVERLAP_SHA256 @@ -605,6 +609,30 @@ mod tests { ); } + #[test] + fn service_endpoint_without_authorization_mode_decodes_as_strip() { + use openshell_core::proto::{ServiceAuthorizationMode, ServiceEndpoint}; + + let endpoint = ServiceEndpoint::decode( + legacy_bytes(SERVICE_ENDPOINT_WITHOUT_AUTHORIZATION_MODE).as_slice(), + ) + .expect("stored service endpoint without authorization mode must decode"); + + assert_eq!(endpoint.sandbox_id, "sandbox-id"); + assert_eq!(endpoint.sandbox, "sandbox"); + assert_eq!(endpoint.name, "web"); + assert_eq!(endpoint.target_port, 8080); + assert!(endpoint.domain); + assert_eq!( + endpoint.authorization_mode(), + ServiceAuthorizationMode::Unspecified + ); + assert_eq!( + crate::service_routing::effective_authorization_mode(endpoint.authorization_mode), + ServiceAuthorizationMode::Strip + ); + } + #[test] fn pre_readiness_sandbox_spec_decodes_with_initial_attachment_epoch() { let spec = openshell_core::proto::SandboxSpec::decode( diff --git a/docs/how-it-works/sandboxes/overview.mdx b/docs/how-it-works/sandboxes/overview.mdx index 09f2e65fa8..8194b0df5b 100644 --- a/docs/how-it-works/sandboxes/overview.mdx +++ b/docs/how-it-works/sandboxes/overview.mdx @@ -478,13 +478,16 @@ name selects the unnamed endpoint. The returned sandbox includes a service URL map keyed by those names; use the empty key for the unnamed endpoint: ```python -from openshell import SandboxClient, ServiceExposure +from openshell import SandboxClient, ServiceAuthorizationMode, ServiceExposure with SandboxClient.from_active_cluster() as client: sandbox = client.create( workspace="default", name="app-server", - service_exposures=[ServiceExposure(target_port=4500)], + service_exposures=[ServiceExposure( + target_port=4500, + authorization_mode=ServiceAuthorizationMode.BEARER_PASSTHROUGH, + )], ) print(sandbox.service_urls[""]) ``` @@ -493,7 +496,10 @@ with SandboxClient.from_active_cluster() as client: const sandbox = await client.sandbox.create({ name: 'app-server', image: 'base', - serviceExposures: [{ targetPort: 4500 }], + serviceExposures: [{ + targetPort: 4500, + authorizationMode: ServiceAuthorizationMode.BearerPassthrough, + }], }) console.log(sandbox.serviceUrls['']) ``` @@ -502,7 +508,10 @@ console.log(sandbox.serviceUrls['']) sandbox, err := client.Sandboxes().Create( ctx, "default", "app-server", spec, nil, v1.CreateOptions{ServiceExposures: []v1.ServiceExposure{ - {TargetPort: 4500}, + { + TargetPort: 4500, + AuthorizationMode: v1.ServiceAuthorizationModeBearerPassthrough, + }, }}, ) fmt.Println(sandbox.ServiceURLs[""]) @@ -514,6 +523,7 @@ let sandbox = client.create_sandbox(openshell_sdk::SandboxSpec { service_exposures: vec![openshell_sdk::ServiceExposure { service: String::new(), target_port: 4500, + authorization_mode: openshell_sdk::ServiceAuthorizationMode::BearerPassthrough, }], ..Default::default() }).await?; @@ -532,6 +542,63 @@ Pass an optional service name to create a named service URL: openshell service expose my-sandbox 8080 web ``` +OpenShell strips the incoming `Authorization` header by default. Opt a service +into forwarding one valid bearer credential unchanged when the application +performs its own authentication: + +```shell +openshell service expose my-sandbox 4500 \ + --authorization-mode bearer-passthrough +``` + +For a create-time exposure, use both flags: + +```shell +openshell sandbox create \ + --expose 4500 \ + --expose-authorization-mode bearer-passthrough \ + --detach \ + -- ./authenticated-server +``` + +In `bearer-passthrough` mode, OpenShell accepts zero or one application +`Authorization` header. When present, it must contain a nonempty Bearer +credential. Missing credentials reach the application so it can return its own +authentication response. Duplicate, Basic, and malformed credentials fail with +`400 Bad Request`. Gateway and edge identity headers, proxy authorization, and +edge authentication cookies remain stripped. + + +Bearer passthrough delivers the caller's credential to the sandbox service. +Enable it only when that service is trusted to receive the credential. Exposed +services still use the gateway listener and its TLS configuration in this +release. + + +For example, Codex App Server can keep the raw capability outside the sandbox +and receive only its SHA-256 verifier: + +```shell +APP_SERVER_TOKEN="$(openssl rand -hex 32)" +APP_SERVER_TOKEN_SHA256="$(printf %s "$APP_SERVER_TOKEN" | openssl dgst -sha256 -hex | awk '{print $2}')" + +openshell sandbox create \ + --name codex-server \ + --env "APP_SERVER_TOKEN_SHA256=$APP_SERVER_TOKEN_SHA256" \ + --expose 4500 \ + --expose-authorization-mode bearer-passthrough \ + --detach \ + -- codex app-server \ + --listen ws://127.0.0.1:4500 \ + --ws-auth capability-token \ + --ws-token-sha256 "$APP_SERVER_TOKEN_SHA256" +``` + +Clients send `Authorization: Bearer $APP_SERVER_TOKEN` in the WebSocket +handshake. Codex's WebSocket transport is experimental and unsupported for +production workloads. It authenticates the handshake before the app-server +`initialize` request. + List exposed endpoints: ```shell @@ -552,7 +619,8 @@ openshell service list my-sandbox --output yaml ``` Structured list output contains `services` and `next_page_token` fields. Each -record contains `workspace`, `sandbox`, `service`, `target_port`, and `url`. +record contains `workspace`, `sandbox`, `service`, `target_port`, +`authorization_mode`, and `url`. The unnamed service uses an empty `service` string. Pass the returned token to `--page-token` to continue. An empty result has an empty `services` collection. @@ -571,7 +639,14 @@ openshell service delete my-sandbox ``` -Loopback gateways return local `openshell.localhost` URLs. Remote gateways return HTTPS URLs that require normal gateway authentication. For gateway service-domain configuration, refer to [Manage Gateways](/how-it-works/gateways/overview#configure-service-forwarding). +Loopback gateways return local `openshell.localhost` URLs. Remote gateways +return HTTPS URLs on the gateway listener. Service routes bypass control-plane +RPC authorization and do not use OIDC or CLI login. Because they share the +listener in this release, its TLS configuration—including any required client +certificate—still applies. The application is responsible for authentication +enabled on its endpoint, and an upstream edge proxy may still apply its own +access policy. For gateway service-domain configuration, refer to +[Manage Gateways](/how-it-works/gateways/overview#configure-service-forwarding). ## Monitor and Debug diff --git a/e2e/rust/Cargo.lock b/e2e/rust/Cargo.lock index c5235dff8b..018ae54912 100644 --- a/e2e/rust/Cargo.lock +++ b/e2e/rust/Cargo.lock @@ -38,7 +38,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" dependencies = [ "aws-lc-sys", - "untrusted", + "untrusted 0.7.1", "zeroize", ] @@ -278,7 +278,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -814,7 +814,7 @@ checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -902,6 +902,8 @@ dependencies = [ "noyalib", "prost", "rand", + "rustls", + "rustls-pemfile", "serde", "serde_json", "serial_test", @@ -909,6 +911,7 @@ dependencies = [ "sha2", "tempfile", "tokio", + "tokio-rustls", "tokio-stream", "tonic", "tonic-prost", @@ -1111,6 +1114,20 @@ dependencies = [ "bitflags", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-hash" version = "2.1.3" @@ -1127,7 +1144,52 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted 0.9.0", ] [[package]] @@ -1317,7 +1379,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1326,6 +1388,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.119" @@ -1375,7 +1443,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1452,7 +1520,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1466,6 +1534,16 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "tokio-rustls" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.19" @@ -1617,6 +1695,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -1738,6 +1822,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -1747,6 +1840,70 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "wit-bindgen" version = "0.57.1" diff --git a/e2e/rust/Cargo.toml b/e2e/rust/Cargo.toml index 4d0522ea86..a2a552400f 100644 --- a/e2e/rust/Cargo.toml +++ b/e2e/rust/Cargo.toml @@ -63,6 +63,11 @@ name = "custom_image" path = "tests/custom_image.rs" required-features = ["e2e-docker"] +[[test]] +name = "service_bearer_passthrough" +path = "tests/service_bearer_passthrough.rs" +required-features = ["e2e-docker"] + [[test]] name = "rootfs_tar" path = "tests/rootfs_tar.rs" @@ -240,11 +245,14 @@ sha1 = "0.10" sha2 = "0.10" hex = "0.4" rand = "0.9" +rustls = { version = "0.23", default-features = false, features = ["std", "logging", "tls12", "aws_lc_rs"] } +rustls-pemfile = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" serde_yml = { package = "noyalib", version = "0.0.28", default-features = false, features = ["std", "compat-serde-yaml"] } tonic = { version = "0.14", features = ["transport"] } tonic-prost = "0.14" +tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "tls12", "aws_lc_rs"] } tower = "0.5" url = "2" nix = { version = "0.29", features = ["process", "signal", "term", "user"] } diff --git a/e2e/rust/tests/provider_readiness.rs b/e2e/rust/tests/provider_readiness.rs index 700c4880da..8ec6398a2b 100644 --- a/e2e/rust/tests/provider_readiness.rs +++ b/e2e/rust/tests/provider_readiness.rs @@ -477,9 +477,12 @@ impl Backend { async fn spawn(&mut self, base: &str, tls_directory: &Path) -> Result { let tls_directory = tls_directory .to_str() - .filter(|path| !path.contains([',', '\n', '\r'])) + .filter(|path| !path.contains([':', '\n', '\r'])) .ok_or("fixture TLS mount path is invalid")?; - let mount = format!("type=bind,src={tls_directory},dst=/fixture-tls,readonly"); + // Docker's structured `--mount` syntax cannot request SELinux + // relabeling. Both fixture backends mount this ephemeral directory, so + // use the shared `z` label rather than the single-container `Z` label. + let mount = format!("{tls_directory}:/fixture-tls:ro,z"); let namespace_label = format!("openshell.ai/sandbox-namespace={}", self.namespace); let mut command = Command::from(self.engine.command()); command @@ -501,7 +504,7 @@ impl Backend { "--read-only", "--cap-drop=ALL", "--security-opt=no-new-privileges:true", - "--mount", + "--volume", &mount, "--entrypoint", "/usr/bin/python3", diff --git a/e2e/rust/tests/service_bearer_passthrough.rs b/e2e/rust/tests/service_bearer_passthrough.rs new file mode 100644 index 0000000000..af2fddb76d --- /dev/null +++ b/e2e/rust/tests/service_bearer_passthrough.rs @@ -0,0 +1,344 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +#![cfg(feature = "e2e-docker")] + +//! Verifies application bearer authorization behavior through an exposed +//! `OpenShell` service. + +use std::fs::File; +use std::io::BufReader; +use std::net::Ipv4Addr; +use std::path::{Path, PathBuf}; +use std::process::Stdio; +use std::sync::Arc; +use std::time::Duration; + +use bytes::Bytes; +use http_body_util::{BodyExt as _, Empty}; +use hyper::client::conn::http1; +use hyper::{Request, StatusCode, header}; +use hyper_util::rt::TokioIo; +use openshell_e2e::harness::binary::openshell_cmd; +use openshell_e2e::harness::sandbox::{E2E_WORKLOAD_IMAGE, SandboxGuard}; +use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName}; +use rustls::{ClientConfig, RootCertStore}; +use serde_json::Value; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio::net::TcpStream; +use tokio::time::{sleep, timeout}; +use tokio_rustls::TlsConnector; +use url::Position; + +const SERVICE_PORT: &str = "4500"; +const BEARER_TOKEN: &str = "Bearer openshell-e2e-application-token"; +const READY_TIMEOUT: Duration = Duration::from_secs(60); +const HEADER_ECHO_SERVER: &str = r#" +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer + +class Handler(BaseHTTPRequestHandler): + def do_GET(self): + body = self.headers.get("Authorization", "").encode() + self.send_response(200) + self.send_header("Content-Type", "text/plain") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def log_message(self, _format, *_args): + pass + +ThreadingHTTPServer(("127.0.0.1", 4500), Handler).serve_forever() +"#; + +async fn run_cli(args: &[&str]) -> Result { + let mut command = openshell_cmd(); + command + .args(args) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + command + .output() + .await + .map_err(|error| format!("failed to run openshell: {error}")) +} + +enum ServiceTransport { + Http, + Https { + connector: TlsConnector, + server_name: ServerName<'static>, + }, +} + +struct ServiceTarget { + port: u16, + authority: String, + path: String, + transport: ServiceTransport, +} + +impl ServiceTarget { + fn from_url(url: &str) -> Result { + let url = url::Url::parse(url).map_err(|error| format!("invalid service URL: {error}"))?; + let host = url + .host_str() + .ok_or_else(|| "service URL omitted its host".to_string())?; + let port = url + .port_or_known_default() + .ok_or_else(|| "service URL omitted its port".to_string())?; + let transport = match url.scheme() { + "http" => ServiceTransport::Http, + "https" => ServiceTransport::Https { + connector: e2e_tls_connector()?, + server_name: ServerName::try_from(host.to_string()) + .map_err(|error| format!("invalid service TLS server name: {error}"))?, + }, + scheme => return Err(format!("unsupported service URL scheme {scheme:?}")), + }; + let authority = url[Position::BeforeHost..Position::AfterPort].to_string(); + let path = url.query().map_or_else( + || url.path().to_string(), + |query| format!("{}?{query}", url.path()), + ); + + Ok(Self { + port, + authority, + path, + transport, + }) + } +} + +fn e2e_mtls_dir() -> Result { + let config_home = std::env::var_os("XDG_CONFIG_HOME") + .ok_or_else(|| "XDG_CONFIG_HOME is required for an HTTPS service URL".to_string())?; + let gateway = std::env::var("OPENSHELL_GATEWAY") + .map_err(|_| "OPENSHELL_GATEWAY is required for an HTTPS service URL".to_string())?; + Ok(PathBuf::from(config_home) + .join("openshell/gateways") + .join(gateway) + .join("mtls")) +} + +fn load_certificates( + path: &Path, + description: &str, +) -> Result>, String> { + let file = File::open(path) + .map_err(|error| format!("open {description} '{}': {error}", path.display()))?; + let mut reader = BufReader::new(file); + rustls_pemfile::certs(&mut reader) + .collect::, _>>() + .map_err(|error| format!("parse {description} '{}': {error}", path.display())) +} + +fn load_private_key(path: &Path) -> Result, String> { + let file = File::open(path) + .map_err(|error| format!("open client TLS key '{}': {error}", path.display()))?; + let mut reader = BufReader::new(file); + rustls_pemfile::private_key(&mut reader) + .map_err(|error| format!("parse client TLS key '{}': {error}", path.display()))? + .ok_or_else(|| format!("client TLS key '{}' is empty", path.display())) +} + +fn e2e_tls_connector() -> Result { + let mtls_dir = e2e_mtls_dir()?; + let ca_path = mtls_dir.join("ca.crt"); + let mut roots = RootCertStore::empty(); + for certificate in load_certificates(&ca_path, "gateway CA certificate")? { + roots.add(certificate).map_err(|error| { + format!( + "add gateway CA certificate '{}': {error}", + ca_path.display() + ) + })?; + } + let client_certificates = + load_certificates(&mtls_dir.join("tls.crt"), "client TLS certificate")?; + let client_key = load_private_key(&mtls_dir.join("tls.key"))?; + let config = ClientConfig::builder() + .with_root_certificates(roots) + .with_client_auth_cert(client_certificates, client_key) + .map_err(|error| format!("build e2e mTLS client configuration: {error}"))?; + Ok(TlsConnector::from(Arc::new(config))) +} + +async fn request_over_stream( + stream: S, + target: &ServiceTarget, + authorization: &str, +) -> Result<(StatusCode, String), String> +where + S: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ + let (mut sender, connection) = http1::handshake(TokioIo::new(stream)) + .await + .map_err(|error| format!("start HTTP connection: {error}"))?; + tokio::spawn(async move { + let _ = connection.await; + }); + + let request = Request::builder() + .uri(&target.path) + .header(header::HOST, &target.authority) + .header(header::AUTHORIZATION, authorization) + .body(Empty::::new()) + .map_err(|error| format!("build service request: {error}"))?; + let response = sender + .send_request(request) + .await + .map_err(|error| format!("send service request: {error}"))?; + let status = response.status(); + let body = response + .into_body() + .collect() + .await + .map_err(|error| format!("read service response: {error}"))? + .to_bytes(); + let body = String::from_utf8(body.to_vec()) + .map_err(|error| format!("service returned non-UTF-8 data: {error}"))?; + Ok((status, body)) +} + +async fn request_service( + target: &ServiceTarget, + authorization: &str, +) -> Result<(StatusCode, String), String> { + // The service hostname is a virtual routing authority. Dial the loopback + // gateway directly so the test does not depend on the host resolver + // recognizing arbitrary subdomains of `.localhost`. + let stream = TcpStream::connect((Ipv4Addr::LOCALHOST, target.port)) + .await + .map_err(|error| format!("connect to loopback service gateway: {error}"))?; + let _ = stream.set_nodelay(true); + match &target.transport { + ServiceTransport::Http => request_over_stream(stream, target, authorization).await, + ServiceTransport::Https { + connector, + server_name, + } => { + let stream = connector + .connect(server_name.clone(), stream) + .await + .map_err(|error| format!("start service TLS connection: {error}"))?; + request_over_stream(stream, target, authorization).await + } + } +} + +async fn wait_for_authorization(url: &str, expected: &str) -> Result<(), String> { + // Parse the URL and load TLS material before the retry loop so permanent + // test-configuration errors fail immediately instead of looking like + // service-readiness timeouts. + let target = ServiceTarget::from_url(url)?; + let mut last_observation = "no request attempted".to_string(); + let result = timeout(READY_TIMEOUT, async { + loop { + match request_service(&target, BEARER_TOKEN).await { + Ok((StatusCode::OK, body)) if body == expected => return Ok(()), + Ok((StatusCode::OK, body)) => { + return Err(format!( + "service received unexpected Authorization value: {body:?}" + )); + } + Ok((status, body)) + if matches!( + status, + StatusCode::BAD_GATEWAY + | StatusCode::PRECONDITION_FAILED + | StatusCode::SERVICE_UNAVAILABLE + ) => + { + last_observation = + format!("service returned retryable status {status} with body {body:?}"); + sleep(Duration::from_millis(250)).await; + } + Err(error) => { + last_observation = error; + sleep(Duration::from_millis(250)).await; + } + Ok((status, body)) => { + return Err(format!( + "service returned unexpected status {status} with body {body:?}" + )); + } + } + } + }) + .await; + + match result { + Ok(result) => result, + Err(_) => Err(format!( + "timed out waiting for the exposed service; last observation: {last_observation}" + )), + } +} + +#[tokio::test] +async fn service_bearer_passthrough_preserves_authorization_header() { + let sandbox_name = format!("service-auth-{}", std::process::id()); + let create = run_cli(&[ + "sandbox", + "create", + "--name", + &sandbox_name, + "--from", + E2E_WORKLOAD_IMAGE, + "--expose", + SERVICE_PORT, + "--output", + "json", + "--detach", + "--no-tty", + "--", + "python3", + "-c", + HEADER_ECHO_SERVER, + ]) + .await + .expect("run sandbox create"); + assert!( + create.status.success(), + "sandbox create failed with exit {:?}: {}", + create.status.code(), + String::from_utf8_lossy(&create.stderr) + ); + let mut sandbox = SandboxGuard::manage_existing(sandbox_name.clone()); + + let created: Value = serde_json::from_slice(&create.stdout).expect("parse sandbox create JSON"); + let service_url = created + .get("service_urls") + .and_then(|urls| urls.get("")) + .and_then(Value::as_str) + .expect("unnamed service URL in create response"); + + wait_for_authorization(service_url, "") + .await + .expect("default mode should strip Authorization"); + + let expose = run_cli(&[ + "service", + "expose", + &sandbox_name, + SERVICE_PORT, + "--authorization-mode", + "bearer-passthrough", + ]) + .await + .expect("run service re-expose"); + assert!( + expose.status.success(), + "service re-expose failed with exit {:?}: {}", + expose.status.code(), + String::from_utf8_lossy(&expose.stderr) + ); + + wait_for_authorization(service_url, BEARER_TOKEN) + .await + .expect("passthrough mode should preserve Authorization"); + + sandbox.cleanup().await; +} diff --git a/proto/openshell.proto b/proto/openshell.proto index 0eaf469f83..83ffbe75ad 100644 --- a/proto/openshell.proto +++ b/proto/openshell.proto @@ -1774,6 +1774,8 @@ message ExposeServiceRequest { // Optional nonzero UUID for durable at-most-once admission. Successful results // can be replayed for 24 hours; see the API errors and retries reference. string request_id = 6; + // Application authorization behavior. Omission resolves to STRIP. + ServiceAuthorizationMode authorization_mode = 7; } // Request to fetch an exposed sandbox service endpoint. @@ -1840,6 +1842,8 @@ message ServiceEndpoint { uint32 target_port = 5; // Whether browser-facing service routing is enabled for this endpoint. bool domain = 6; + // Effective application authorization behavior for ingress requests. + ServiceAuthorizationMode authorization_mode = 7; } // Response containing a service endpoint and, when available, its local URL. @@ -3785,4 +3789,17 @@ message SandboxServiceExposure { string service = 1; // Loopback TCP port inside the sandbox. uint32 target_port = 2; + // Application authorization behavior. Omission resolves to STRIP. + ServiceAuthorizationMode authorization_mode = 3; +} + +// Controls whether an exposed service receives the request's application +// Authorization header. +enum ServiceAuthorizationMode { + // Omission preserves the secure legacy behavior and resolves to STRIP. + SERVICE_AUTHORIZATION_MODE_UNSPECIFIED = 0; + // Remove Authorization before forwarding to the sandbox service. + SERVICE_AUTHORIZATION_MODE_STRIP = 1; + // Forward one syntactically valid bearer Authorization header unchanged. + SERVICE_AUTHORIZATION_MODE_BEARER_PASSTHROUGH = 2; } diff --git a/python/openshell/__init__.py b/python/openshell/__init__.py index 8d0d20f578..109fe09521 100644 --- a/python/openshell/__init__.py +++ b/python/openshell/__init__.py @@ -21,6 +21,7 @@ SandboxStatusRef, SandboxTemplateClient, SandboxWorkloadTemplateProvenanceRef, + ServiceAuthorizationMode, ServiceExposure, TlsConfig, WorkspaceClient, @@ -53,6 +54,7 @@ "SandboxStatusRef", "SandboxTemplateClient", "SandboxWorkloadTemplateProvenanceRef", + "ServiceAuthorizationMode", "ServiceExposure", "TlsConfig", "WorkspaceClient", diff --git a/python/openshell/sandbox.py b/python/openshell/sandbox.py index 20d4734443..485af39d04 100644 --- a/python/openshell/sandbox.py +++ b/python/openshell/sandbox.py @@ -17,6 +17,7 @@ import time from collections import namedtuple from dataclasses import dataclass, field +from enum import IntEnum from typing import TYPE_CHECKING, Any, Generic, Never, SupportsIndex, TypeVar, cast from urllib.parse import urlparse @@ -115,6 +116,12 @@ def _service_exposure_messages( openshell_pb2.SandboxServiceExposure( service=exposure.service, target_port=exposure.target_port, + authorization_mode=( + openshell_pb2.SERVICE_AUTHORIZATION_MODE_BEARER_PASSTHROUGH + if exposure.authorization_mode + == ServiceAuthorizationMode.BEARER_PASSTHROUGH + else openshell_pb2.SERVICE_AUTHORIZATION_MODE_STRIP + ), ) for exposure in exposures or () ] @@ -453,12 +460,20 @@ class SandboxStatusRef: main_process_started_at_ms: int | None = None +class ServiceAuthorizationMode(IntEnum): + """Handling for an incoming application Authorization header.""" + + STRIP = 1 + BEARER_PASSTHROUGH = 2 + + @dataclass(frozen=True) class ServiceExposure: """A loopback HTTP service to expose during sandbox creation.""" target_port: int service: str = "" + authorization_mode: ServiceAuthorizationMode = ServiceAuthorizationMode.STRIP class _ImmutableLabels(dict[str, str]): diff --git a/python/openshell/sandbox_test.py b/python/openshell/sandbox_test.py index 7702b9fcd7..3b744451cb 100644 --- a/python/openshell/sandbox_test.py +++ b/python/openshell/sandbox_test.py @@ -32,6 +32,7 @@ SandboxRef, SandboxStatusRef, SandboxTemplateClient, + ServiceAuthorizationMode, ServiceExposure, TlsConfig, _atomic_replace, @@ -2219,15 +2220,26 @@ def test_create_forwards_service_exposures() -> None: name="app-server", service_exposures=[ ServiceExposure(target_port=4500), - ServiceExposure(service="metrics", target_port=9090), + ServiceExposure( + service="metrics", + target_port=9090, + authorization_mode=ServiceAuthorizationMode.BEARER_PASSTHROUGH, + ), ], ) assert stub.create_request is not None assert [ - (exposure.service, exposure.target_port) + (exposure.service, exposure.target_port, exposure.authorization_mode) for exposure in stub.create_request.service_exposures - ] == [("", 4500), ("metrics", 9090)] + ] == [ + ("", 4500, openshell_pb2.SERVICE_AUTHORIZATION_MODE_STRIP), + ( + "metrics", + 9090, + openshell_pb2.SERVICE_AUTHORIZATION_MODE_BEARER_PASSTHROUGH, + ), + ] assert dict(ref.service_urls) == { "": "https://.example.test/", "metrics": "https://metrics.example.test/", diff --git a/sdk/go/openshell/v1/fake/service.go b/sdk/go/openshell/v1/fake/service.go index 464d8f7fc9..3d7a1e0331 100644 --- a/sdk/go/openshell/v1/fake/service.go +++ b/sdk/go/openshell/v1/fake/service.go @@ -22,7 +22,7 @@ func newFakeServiceClient(closedFunc func() bool) *fakeServiceClient { } // Expose returns Unimplemented. -func (c *fakeServiceClient) Expose(_ context.Context, _, _, _ string, _ uint32, _ bool) (*types.ServiceEndpoint, error) { +func (c *fakeServiceClient) Expose(_ context.Context, _, _, _ string, _ uint32, _ bool, _ ...v1.ExposeServiceOptions) (*types.ServiceEndpoint, error) { if c.closedFunc() { return nil, &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} } diff --git a/sdk/go/openshell/v1/internal/converter/service.go b/sdk/go/openshell/v1/internal/converter/service.go index be50519613..bb2803c6bb 100644 --- a/sdk/go/openshell/v1/internal/converter/service.go +++ b/sdk/go/openshell/v1/internal/converter/service.go @@ -26,6 +26,7 @@ func ServiceEndpointFromProto(resp *pb.ServiceEndpointResponse) *types.ServiceEn result.Name = ep.GetName() result.TargetPort = ep.GetTargetPort() result.Domain = ep.GetDomain() + result.AuthorizationMode = serviceAuthorizationModeFromProto(ep.GetAuthorizationMode()) if m := ep.GetMetadata(); m != nil { result.ID = m.GetId() @@ -48,12 +49,27 @@ func ServiceEndpointToProto(se *types.ServiceEndpoint) *pb.ServiceEndpointRespon Id: se.ID, Workspace: se.Workspace, }, - SandboxId: se.SandboxID, - Sandbox: se.Sandbox, - Name: se.Name, - TargetPort: se.TargetPort, - Domain: se.Domain, + SandboxId: se.SandboxID, + Sandbox: se.Sandbox, + Name: se.Name, + TargetPort: se.TargetPort, + Domain: se.Domain, + AuthorizationMode: serviceAuthorizationModeToProto(se.AuthorizationMode), }, Url: se.URL, } } + +func serviceAuthorizationModeToProto(mode types.ServiceAuthorizationMode) pb.ServiceAuthorizationMode { + if mode == types.ServiceAuthorizationModeBearerPassthrough { + return pb.ServiceAuthorizationMode_SERVICE_AUTHORIZATION_MODE_BEARER_PASSTHROUGH + } + return pb.ServiceAuthorizationMode_SERVICE_AUTHORIZATION_MODE_STRIP +} + +func serviceAuthorizationModeFromProto(mode pb.ServiceAuthorizationMode) types.ServiceAuthorizationMode { + if mode == pb.ServiceAuthorizationMode_SERVICE_AUTHORIZATION_MODE_BEARER_PASSTHROUGH { + return types.ServiceAuthorizationModeBearerPassthrough + } + return types.ServiceAuthorizationModeStrip +} diff --git a/sdk/go/openshell/v1/internal/converter/service_test.go b/sdk/go/openshell/v1/internal/converter/service_test.go index ec5fa540a4..6db09701e3 100644 --- a/sdk/go/openshell/v1/internal/converter/service_test.go +++ b/sdk/go/openshell/v1/internal/converter/service_test.go @@ -19,11 +19,12 @@ func TestServiceEndpointFromProto(t *testing.T) { Metadata: &dm.ObjectMeta{ Id: "svc-1", }, - SandboxId: "sb-1", - Sandbox: "my-sandbox", - Name: "http-server", - TargetPort: 8080, - Domain: true, + SandboxId: "sb-1", + Sandbox: "my-sandbox", + Name: "http-server", + TargetPort: 8080, + Domain: true, + AuthorizationMode: pb.ServiceAuthorizationMode_SERVICE_AUTHORIZATION_MODE_BEARER_PASSTHROUGH, }, Url: "https://svc-1.example.com", } @@ -37,6 +38,7 @@ func TestServiceEndpointFromProto(t *testing.T) { assert.Equal(t, "http-server", se.Name) assert.Equal(t, uint32(8080), se.TargetPort) assert.True(t, se.Domain) + assert.Equal(t, v1.ServiceAuthorizationModeBearerPassthrough, se.AuthorizationMode) assert.Equal(t, "https://svc-1.example.com", se.URL) } @@ -78,13 +80,14 @@ func TestServiceEndpointFromProto_Nil(t *testing.T) { func TestServiceEndpointToProto(t *testing.T) { se := &v1.ServiceEndpoint{ - ID: "svc-1", - SandboxID: "sb-1", - Sandbox: "my-sandbox", - Name: "http-server", - TargetPort: 8080, - Domain: true, - URL: "https://svc-1.example.com", + ID: "svc-1", + SandboxID: "sb-1", + Sandbox: "my-sandbox", + Name: "http-server", + TargetPort: 8080, + Domain: true, + AuthorizationMode: v1.ServiceAuthorizationModeBearerPassthrough, + URL: "https://svc-1.example.com", } resp := ServiceEndpointToProto(se) @@ -98,6 +101,7 @@ func TestServiceEndpointToProto(t *testing.T) { assert.Equal(t, "http-server", resp.Endpoint.Name) assert.Equal(t, uint32(8080), resp.Endpoint.TargetPort) assert.True(t, resp.Endpoint.Domain) + assert.Equal(t, pb.ServiceAuthorizationMode_SERVICE_AUTHORIZATION_MODE_BEARER_PASSTHROUGH, resp.Endpoint.AuthorizationMode) assert.Equal(t, "https://svc-1.example.com", resp.Url) } @@ -108,13 +112,14 @@ func TestServiceEndpointToProto_Nil(t *testing.T) { func TestServiceEndpointRoundTrip(t *testing.T) { original := &v1.ServiceEndpoint{ - ID: "svc-rt", - SandboxID: "sb-rt", - Sandbox: "round-trip", - Name: "web", - TargetPort: 9090, - Domain: false, - URL: "http://localhost:9090", + ID: "svc-rt", + SandboxID: "sb-rt", + Sandbox: "round-trip", + Name: "web", + TargetPort: 9090, + Domain: false, + AuthorizationMode: v1.ServiceAuthorizationModeBearerPassthrough, + URL: "http://localhost:9090", } proto := ServiceEndpointToProto(original) @@ -127,5 +132,6 @@ func TestServiceEndpointRoundTrip(t *testing.T) { assert.Equal(t, original.Name, back.Name) assert.Equal(t, original.TargetPort, back.TargetPort) assert.Equal(t, original.Domain, back.Domain) + assert.Equal(t, original.AuthorizationMode, back.AuthorizationMode) assert.Equal(t, original.URL, back.URL) } diff --git a/sdk/go/openshell/v1/sandbox_client.go b/sdk/go/openshell/v1/sandbox_client.go index 9868d11b93..f8b8290ca1 100644 --- a/sdk/go/openshell/v1/sandbox_client.go +++ b/sdk/go/openshell/v1/sandbox_client.go @@ -91,13 +91,21 @@ func serviceExposuresToProto(exposures []types.ServiceExposure) []*pb.SandboxSer result := make([]*pb.SandboxServiceExposure, 0, len(exposures)) for _, exposure := range exposures { result = append(result, &pb.SandboxServiceExposure{ - Service: exposure.Service, - TargetPort: exposure.TargetPort, + Service: exposure.Service, + TargetPort: exposure.TargetPort, + AuthorizationMode: serviceAuthorizationModeToProto(exposure.AuthorizationMode), }) } return result } +func serviceAuthorizationModeToProto(mode types.ServiceAuthorizationMode) pb.ServiceAuthorizationMode { + if mode == 0 { + return pb.ServiceAuthorizationMode_SERVICE_AUTHORIZATION_MODE_STRIP + } + return pb.ServiceAuthorizationMode(mode) +} + func validateTemplateCreateSpec(spec *SandboxSpec) error { if spec == nil { return nil diff --git a/sdk/go/openshell/v1/sandbox_client_test.go b/sdk/go/openshell/v1/sandbox_client_test.go index 8ae398f9e0..70c3b6fab2 100644 --- a/sdk/go/openshell/v1/sandbox_client_test.go +++ b/sdk/go/openshell/v1/sandbox_client_test.go @@ -318,7 +318,11 @@ func TestSandboxCreate(t *testing.T) { labels, CreateOptions{ServiceExposures: []ServiceExposure{ {TargetPort: 4500}, - {Service: "metrics", TargetPort: 9090}, + { + Service: "metrics", + TargetPort: 9090, + AuthorizationMode: ServiceAuthorizationModeBearerPassthrough, + }, }}, ) @@ -334,7 +338,9 @@ func TestSandboxCreate(t *testing.T) { }, result.ServiceURLs) require.Len(t, mock.createRequest.GetServiceExposures(), 2) assert.Equal(t, uint32(4500), mock.createRequest.GetServiceExposures()[0].GetTargetPort()) + assert.Equal(t, pb.ServiceAuthorizationMode_SERVICE_AUTHORIZATION_MODE_STRIP, mock.createRequest.GetServiceExposures()[0].GetAuthorizationMode()) assert.Equal(t, "metrics", mock.createRequest.GetServiceExposures()[1].GetService()) + assert.Equal(t, pb.ServiceAuthorizationMode_SERVICE_AUTHORIZATION_MODE_BEARER_PASSTHROUGH, mock.createRequest.GetServiceExposures()[1].GetAuthorizationMode()) } func TestSandboxCreate_DefaultGPURequest(t *testing.T) { diff --git a/sdk/go/openshell/v1/service.go b/sdk/go/openshell/v1/service.go index 3ef3bea2fc..ccaf93ee4b 100644 --- a/sdk/go/openshell/v1/service.go +++ b/sdk/go/openshell/v1/service.go @@ -15,9 +15,24 @@ type ServiceEndpoint = types.ServiceEndpoint // ServiceExposure describes a loopback HTTP service to expose during sandbox creation. type ServiceExposure = types.ServiceExposure +// ServiceAuthorizationMode controls handling of an incoming application Authorization header. +type ServiceAuthorizationMode = types.ServiceAuthorizationMode + +const ( + // ServiceAuthorizationModeStrip removes Authorization before proxying to the service. + ServiceAuthorizationModeStrip = types.ServiceAuthorizationModeStrip + // ServiceAuthorizationModeBearerPassthrough forwards one valid bearer credential unchanged. + ServiceAuthorizationModeBearerPassthrough = types.ServiceAuthorizationModeBearerPassthrough +) + +// ExposeServiceOptions configures service exposure behavior. +type ExposeServiceOptions struct { + AuthorizationMode ServiceAuthorizationMode +} + // ServiceInterface defines operations for managing sandbox service endpoints. type ServiceInterface interface { - Expose(ctx context.Context, workspace, sandboxName, serviceName string, targetPort uint32, domain bool) (*ServiceEndpoint, error) + Expose(ctx context.Context, workspace, sandboxName, serviceName string, targetPort uint32, domain bool, opts ...ExposeServiceOptions) (*ServiceEndpoint, error) Get(ctx context.Context, workspace, sandboxName, serviceName string) (*ServiceEndpoint, error) List(workspace, sandboxName string, opts ...ListOptions) (*Pager[*ServiceEndpoint], error) ListAll(ctx context.Context, workspace, sandboxName string, opts ...ListOptions) ([]*ServiceEndpoint, error) diff --git a/sdk/go/openshell/v1/service_client.go b/sdk/go/openshell/v1/service_client.go index 80eb2bbab7..5606ebf2a0 100644 --- a/sdk/go/openshell/v1/service_client.go +++ b/sdk/go/openshell/v1/service_client.go @@ -19,13 +19,18 @@ func newServiceClient(conn grpc.ClientConnInterface) *serviceClient { return &serviceClient{client: pb.NewOpenShellClient(conn)} } -func (s *serviceClient) Expose(ctx context.Context, workspace, sandboxName, serviceName string, targetPort uint32, domain bool) (*ServiceEndpoint, error) { +func (s *serviceClient) Expose(ctx context.Context, workspace, sandboxName, serviceName string, targetPort uint32, domain bool, opts ...ExposeServiceOptions) (*ServiceEndpoint, error) { + authorizationMode := ServiceAuthorizationModeStrip + if len(opts) > 0 && opts[0].AuthorizationMode != 0 { + authorizationMode = opts[0].AuthorizationMode + } resp, err := s.client.ExposeService(ctx, &pb.ExposeServiceRequest{ - Sandbox: sandboxName, - WorkspaceScope: namedWorkspaceScope(workspace), - Name: serviceName, - TargetPort: targetPort, - Domain: domain, + Sandbox: sandboxName, + WorkspaceScope: namedWorkspaceScope(workspace), + Name: serviceName, + TargetPort: targetPort, + Domain: domain, + AuthorizationMode: pb.ServiceAuthorizationMode(authorizationMode), }) if err != nil { return nil, converter.FromGRPCError(err) diff --git a/sdk/go/openshell/v1/service_client_test.go b/sdk/go/openshell/v1/service_client_test.go index 9b702dbde6..3ba2e5e69a 100644 --- a/sdk/go/openshell/v1/service_client_test.go +++ b/sdk/go/openshell/v1/service_client_test.go @@ -55,10 +55,11 @@ func (s *mockServiceServer) ExposeService(_ context.Context, req *pb.ExposeServi Metadata: &dm.ObjectMeta{ Id: "ep-" + req.GetName(), }, - Sandbox: req.GetSandbox(), - Name: req.GetName(), - TargetPort: req.GetTargetPort(), - Domain: req.GetDomain(), + Sandbox: req.GetSandbox(), + Name: req.GetName(), + TargetPort: req.GetTargetPort(), + Domain: req.GetDomain(), + AuthorizationMode: req.GetAuthorizationMode(), }, } if req.GetDomain() { @@ -150,7 +151,15 @@ func TestServiceExpose(t *testing.T) { client, cleanup := setupServiceTest(t, mock) defer cleanup() - ep, err := client.Expose(context.Background(), "default", "web-app", "api", 8080, true) + ep, err := client.Expose( + context.Background(), + "default", + "web-app", + "api", + 8080, + true, + ExposeServiceOptions{AuthorizationMode: ServiceAuthorizationModeBearerPassthrough}, + ) require.NoError(t, err) require.NotNil(t, ep) @@ -159,6 +168,7 @@ func TestServiceExpose(t *testing.T) { assert.Equal(t, "api", ep.Name) assert.Equal(t, uint32(8080), ep.TargetPort) assert.True(t, ep.Domain) + assert.Equal(t, ServiceAuthorizationModeBearerPassthrough, ep.AuthorizationMode) assert.Equal(t, "https://api.example.com", ep.URL) } diff --git a/sdk/go/openshell/v1/types/service.go b/sdk/go/openshell/v1/types/service.go index 9cccff8258..5ae7aa0b9e 100644 --- a/sdk/go/openshell/v1/types/service.go +++ b/sdk/go/openshell/v1/types/service.go @@ -3,20 +3,32 @@ package types +// ServiceAuthorizationMode controls handling of an incoming application Authorization header. +type ServiceAuthorizationMode int32 + +const ( + // ServiceAuthorizationModeStrip removes Authorization before proxying to the sandbox service. + ServiceAuthorizationModeStrip ServiceAuthorizationMode = 1 + // ServiceAuthorizationModeBearerPassthrough forwards one valid bearer credential unchanged. + ServiceAuthorizationModeBearerPassthrough ServiceAuthorizationMode = 2 +) + // ServiceExposure describes a loopback HTTP service to expose during sandbox creation. type ServiceExposure struct { - Service string - TargetPort uint32 + Service string + TargetPort uint32 + AuthorizationMode ServiceAuthorizationMode } // ServiceEndpoint represents an exposed HTTP service on a sandbox. type ServiceEndpoint struct { - ID string - SandboxID string - Sandbox string - Name string - TargetPort uint32 - Domain bool - URL string - Workspace string + ID string + SandboxID string + Sandbox string + Name string + TargetPort uint32 + Domain bool + URL string + Workspace string + AuthorizationMode ServiceAuthorizationMode } diff --git a/sdk/go/proto/openshellv1/openshell.pb.go b/sdk/go/proto/openshellv1/openshell.pb.go index f4258a87b6..109d2ef454 100644 --- a/sdk/go/proto/openshellv1/openshell.pb.go +++ b/sdk/go/proto/openshellv1/openshell.pb.go @@ -1197,6 +1197,60 @@ func (EndpointResult) EnumDescriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{18} } +// Controls whether an exposed service receives the request's application +// Authorization header. +type ServiceAuthorizationMode int32 + +const ( + // Omission preserves the secure legacy behavior and resolves to STRIP. + ServiceAuthorizationMode_SERVICE_AUTHORIZATION_MODE_UNSPECIFIED ServiceAuthorizationMode = 0 + // Remove Authorization before forwarding to the sandbox service. + ServiceAuthorizationMode_SERVICE_AUTHORIZATION_MODE_STRIP ServiceAuthorizationMode = 1 + // Forward one syntactically valid bearer Authorization header unchanged. + ServiceAuthorizationMode_SERVICE_AUTHORIZATION_MODE_BEARER_PASSTHROUGH ServiceAuthorizationMode = 2 +) + +// Enum value maps for ServiceAuthorizationMode. +var ( + ServiceAuthorizationMode_name = map[int32]string{ + 0: "SERVICE_AUTHORIZATION_MODE_UNSPECIFIED", + 1: "SERVICE_AUTHORIZATION_MODE_STRIP", + 2: "SERVICE_AUTHORIZATION_MODE_BEARER_PASSTHROUGH", + } + ServiceAuthorizationMode_value = map[string]int32{ + "SERVICE_AUTHORIZATION_MODE_UNSPECIFIED": 0, + "SERVICE_AUTHORIZATION_MODE_STRIP": 1, + "SERVICE_AUTHORIZATION_MODE_BEARER_PASSTHROUGH": 2, + } +) + +func (x ServiceAuthorizationMode) Enum() *ServiceAuthorizationMode { + p := new(ServiceAuthorizationMode) + *p = x + return p +} + +func (x ServiceAuthorizationMode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ServiceAuthorizationMode) Descriptor() protoreflect.EnumDescriptor { + return file_openshell_proto_enumTypes[19].Descriptor() +} + +func (ServiceAuthorizationMode) Type() protoreflect.EnumType { + return &file_openshell_proto_enumTypes[19] +} + +func (x ServiceAuthorizationMode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ServiceAuthorizationMode.Descriptor instead. +func (ServiceAuthorizationMode) EnumDescriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{19} +} + // IssueSandboxToken request. Empty body; identity is established by the // authentication credentials carried in the request headers (a projected // Kubernetes ServiceAccount JWT in the K8s driver path). @@ -6107,9 +6161,11 @@ type ExposeServiceRequest struct { Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` // Optional nonzero UUID for durable at-most-once admission. Successful results // can be replayed for 24 hours; see the API errors and retries reference. - RequestId string `protobuf:"bytes,6,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + RequestId string `protobuf:"bytes,6,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + // Application authorization behavior. Omission resolves to STRIP. + AuthorizationMode ServiceAuthorizationMode `protobuf:"varint,7,opt,name=authorization_mode,json=authorizationMode,proto3,enum=openshell.v1.ServiceAuthorizationMode" json:"authorization_mode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ExposeServiceRequest) Reset() { @@ -6184,6 +6240,13 @@ func (x *ExposeServiceRequest) GetRequestId() string { return "" } +func (x *ExposeServiceRequest) GetAuthorizationMode() ServiceAuthorizationMode { + if x != nil { + return x.AuthorizationMode + } + return ServiceAuthorizationMode_SERVICE_AUTHORIZATION_MODE_UNSPECIFIED +} + // Request to fetch an exposed sandbox service endpoint. type GetServiceRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -6516,9 +6579,11 @@ type ServiceEndpoint struct { // Loopback TCP port inside the sandbox. TargetPort uint32 `protobuf:"varint,5,opt,name=target_port,json=targetPort,proto3" json:"target_port,omitempty"` // Whether browser-facing service routing is enabled for this endpoint. - Domain bool `protobuf:"varint,6,opt,name=domain,proto3" json:"domain,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Domain bool `protobuf:"varint,6,opt,name=domain,proto3" json:"domain,omitempty"` + // Effective application authorization behavior for ingress requests. + AuthorizationMode ServiceAuthorizationMode `protobuf:"varint,7,opt,name=authorization_mode,json=authorizationMode,proto3,enum=openshell.v1.ServiceAuthorizationMode" json:"authorization_mode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ServiceEndpoint) Reset() { @@ -6593,6 +6658,13 @@ func (x *ServiceEndpoint) GetDomain() bool { return false } +func (x *ServiceEndpoint) GetAuthorizationMode() ServiceAuthorizationMode { + if x != nil { + return x.AuthorizationMode + } + return ServiceAuthorizationMode_SERVICE_AUTHORIZATION_MODE_UNSPECIFIED +} + // Response containing a service endpoint and, when available, its local URL. type ServiceEndpointResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -17694,9 +17766,11 @@ type SandboxServiceExposure struct { // Service name within the sandbox. Empty selects the unnamed endpoint. Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` // Loopback TCP port inside the sandbox. - TargetPort uint32 `protobuf:"varint,2,opt,name=target_port,json=targetPort,proto3" json:"target_port,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + TargetPort uint32 `protobuf:"varint,2,opt,name=target_port,json=targetPort,proto3" json:"target_port,omitempty"` + // Application authorization behavior. Omission resolves to STRIP. + AuthorizationMode ServiceAuthorizationMode `protobuf:"varint,3,opt,name=authorization_mode,json=authorizationMode,proto3,enum=openshell.v1.ServiceAuthorizationMode" json:"authorization_mode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SandboxServiceExposure) Reset() { @@ -17743,6 +17817,13 @@ func (x *SandboxServiceExposure) GetTargetPort() uint32 { return 0 } +func (x *SandboxServiceExposure) GetAuthorizationMode() ServiceAuthorizationMode { + if x != nil { + return x.AuthorizationMode + } + return ServiceAuthorizationMode_SERVICE_AUTHORIZATION_MODE_UNSPECIFIED +} + var File_openshell_proto protoreflect.FileDescriptor const file_openshell_proto_rawDesc = "" + @@ -18146,7 +18227,7 @@ const file_openshell_proto_rawDesc = "" + "\fgateway_port\x18\x04 \x01(\rR\vgatewayPort\x12%\n" + "\x0egateway_scheme\x18\x05 \x01(\tR\rgatewayScheme\x120\n" + "\x14host_key_fingerprint\x18\a \x01(\tR\x12hostKeyFingerprint\x12C\n" + - "\x0fexpiration_time\x18l \x01(\v2\x1a.google.protobuf.TimestampR\x0eexpirationTimeJ\x04\b\b\x10\tR\rexpires_at_ms\"\xf0\x01\n" + + "\x0fexpiration_time\x18l \x01(\v2\x1a.google.protobuf.TimestampR\x0eexpirationTimeJ\x04\b\b\x10\tR\rexpires_at_ms\"\xc7\x02\n" + "\x14ExposeServiceRequest\x12R\n" + "\x0fworkspace_scope\x18\x05 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScope\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12\x1f\n" + @@ -18155,7 +18236,8 @@ const file_openshell_proto_rawDesc = "" + "\x06domain\x18\x04 \x01(\bR\x06domain\x12\x18\n" + "\asandbox\x18\x01 \x01(\tR\asandbox\x12\x1d\n" + "\n" + - "request_id\x18\x06 \x01(\tR\trequestId\"\x95\x01\n" + + "request_id\x18\x06 \x01(\tR\trequestId\x12U\n" + + "\x12authorization_mode\x18\a \x01(\x0e2&.openshell.v1.ServiceAuthorizationModeR\x11authorizationMode\"\x95\x01\n" + "\x11GetServiceRequest\x12R\n" + "\x0fworkspace_scope\x18\x03 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScope\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12\x18\n" + @@ -18177,7 +18259,7 @@ const file_openshell_proto_rawDesc = "" + "\n" + "request_id\x18\x05 \x01(\tR\trequestId\"_\n" + "\x15DeleteServiceResponse\x127\n" + - "\aoutcome\x18\x02 \x01(\x0e2\x1d.openshell.v1.DeletionOutcomeR\aoutcomeJ\x04\b\x01\x10\x02R\adeleted\"\xd7\x01\n" + + "\aoutcome\x18\x02 \x01(\x0e2\x1d.openshell.v1.DeletionOutcomeR\aoutcomeJ\x04\b\x01\x10\x02R\adeleted\"\xae\x02\n" + "\x0fServiceEndpoint\x12>\n" + "\bmetadata\x18\x01 \x01(\v2\".openshell.datamodel.v1.ObjectMetaR\bmetadata\x12\x1d\n" + "\n" + @@ -18186,7 +18268,8 @@ const file_openshell_proto_rawDesc = "" + "\x04name\x18\x04 \x01(\tR\x04name\x12\x1f\n" + "\vtarget_port\x18\x05 \x01(\rR\n" + "targetPort\x12\x16\n" + - "\x06domain\x18\x06 \x01(\bR\x06domain\"f\n" + + "\x06domain\x18\x06 \x01(\bR\x06domain\x12U\n" + + "\x12authorization_mode\x18\a \x01(\x0e2&.openshell.v1.ServiceAuthorizationModeR\x11authorizationMode\"f\n" + "\x17ServiceEndpointResponse\x129\n" + "\bendpoint\x18\x01 \x01(\v2\x1d.openshell.v1.ServiceEndpointR\bendpoint\x12\x10\n" + "\x03url\x18\x02 \x01(\tR\x03url\"Z\n" + @@ -19072,11 +19155,12 @@ const file_openshell_proto_rawDesc = "" + "\x12cleanup_retry_time\x18\t \x01(\v2\x1a.google.protobuf.TimestampR\x10cleanupRetryTime\x120\n" + "\x14attachment_change_id\x18\n" + " \x01(\tR\x12attachmentChangeId\x12P\n" + - "\x16attachment_change_time\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\x14attachmentChangeTime\"S\n" + + "\x16attachment_change_time\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\x14attachmentChangeTime\"\xaa\x01\n" + "\x16SandboxServiceExposure\x12\x18\n" + "\aservice\x18\x01 \x01(\tR\aservice\x12\x1f\n" + "\vtarget_port\x18\x02 \x01(\rR\n" + - "targetPort*\xca\x01\n" + + "targetPort\x12U\n" + + "\x12authorization_mode\x18\x03 \x01(\x0e2&.openshell.v1.ServiceAuthorizationModeR\x11authorizationMode*\xca\x01\n" + "\rExtensionKind\x12\x1e\n" + "\x1aEXTENSION_KIND_UNSPECIFIED\x10\x00\x12!\n" + "\x1dEXTENSION_KIND_COMPUTE_DRIVER\x10\x01\x12$\n" + @@ -19216,7 +19300,11 @@ const file_openshell_proto_rawDesc = "" + "&ENDPOINT_RESULT_CREDENTIAL_UNAVAILABLE\x10\x04\x12\x1e\n" + "\x1aENDPOINT_RESULT_TLS_FAILED\x10\x05\x12$\n" + " ENDPOINT_RESULT_TRANSPORT_FAILED\x10\x06\x12%\n" + - "!ENDPOINT_RESULT_UPSTREAM_REJECTED\x10\a2\xafU\n" + + "!ENDPOINT_RESULT_UPSTREAM_REJECTED\x10\a*\x9f\x01\n" + + "\x18ServiceAuthorizationMode\x12*\n" + + "&SERVICE_AUTHORIZATION_MODE_UNSPECIFIED\x10\x00\x12$\n" + + " SERVICE_AUTHORIZATION_MODE_STRIP\x10\x01\x121\n" + + "-SERVICE_AUTHORIZATION_MODE_BEARER_PASSTHROUGH\x10\x022\xafU\n" + "\tOpenShell\x12Z\n" + "\x06Health\x12\x1b.openshell.v1.HealthRequest\x1a\x1c.openshell.v1.HealthResponse\"\x15\x82\xb5\x18\x11\n" + "\x0funauthenticated\x12i\n" + @@ -19402,7 +19490,7 @@ func file_openshell_proto_rawDescGZIP() []byte { return file_openshell_proto_rawDescData } -var file_openshell_proto_enumTypes = make([]protoimpl.EnumInfo, 19) +var file_openshell_proto_enumTypes = make([]protoimpl.EnumInfo, 20) var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 255) var file_openshell_proto_goTypes = []any{ (ExtensionKind)(0), // 0: openshell.v1.ExtensionKind @@ -19424,772 +19512,776 @@ var file_openshell_proto_goTypes = []any{ (SandboxRestartPolicy)(0), // 16: openshell.v1.SandboxRestartPolicy (DeletionOutcome)(0), // 17: openshell.v1.DeletionOutcome (EndpointResult)(0), // 18: openshell.v1.EndpointResult - (*IssueSandboxTokenRequest)(nil), // 19: openshell.v1.IssueSandboxTokenRequest - (*IssueSandboxTokenResponse)(nil), // 20: openshell.v1.IssueSandboxTokenResponse - (*RefreshSandboxTokenRequest)(nil), // 21: openshell.v1.RefreshSandboxTokenRequest - (*RefreshSandboxTokenResponse)(nil), // 22: openshell.v1.RefreshSandboxTokenResponse - (*HealthRequest)(nil), // 23: openshell.v1.HealthRequest - (*HealthResponse)(nil), // 24: openshell.v1.HealthResponse - (*GetCurrentUserRequest)(nil), // 25: openshell.v1.GetCurrentUserRequest - (*GetCurrentUserResponse)(nil), // 26: openshell.v1.GetCurrentUserResponse - (*GetGatewayInfoRequest)(nil), // 27: openshell.v1.GetGatewayInfoRequest - (*GetGatewayInfoResponse)(nil), // 28: openshell.v1.GetGatewayInfoResponse - (*NegotiatedExtensionInfo)(nil), // 29: openshell.v1.NegotiatedExtensionInfo - (*ComputeDriverInfo)(nil), // 30: openshell.v1.ComputeDriverInfo - (*ComputeDriverCapabilities)(nil), // 31: openshell.v1.ComputeDriverCapabilities - (*ResourceCapabilities)(nil), // 32: openshell.v1.ResourceCapabilities - (*CpuResourceCapabilities)(nil), // 33: openshell.v1.CpuResourceCapabilities - (*MemoryResourceCapabilities)(nil), // 34: openshell.v1.MemoryResourceCapabilities - (*GpuResourceCapabilities)(nil), // 35: openshell.v1.GpuResourceCapabilities - (*Sandbox)(nil), // 36: openshell.v1.Sandbox - (*SandboxSpec)(nil), // 37: openshell.v1.SandboxSpec - (*ResourceRequirements)(nil), // 38: openshell.v1.ResourceRequirements - (*GpuResourceRequirements)(nil), // 39: openshell.v1.GpuResourceRequirements - (*SandboxTemplate)(nil), // 40: openshell.v1.SandboxTemplate - (*SandboxWorkloadTemplate)(nil), // 41: openshell.v1.SandboxWorkloadTemplate - (*SandboxWorkloadTemplateSpec)(nil), // 42: openshell.v1.SandboxWorkloadTemplateSpec - (*SandboxWorkloadConfig)(nil), // 43: openshell.v1.SandboxWorkloadConfig - (*SandboxResources)(nil), // 44: openshell.v1.SandboxResources - (*SandboxServiceLevel)(nil), // 45: openshell.v1.SandboxServiceLevel - (*SandboxStartup)(nil), // 46: openshell.v1.SandboxStartup - (*SandboxWorkloadTemplateProvenance)(nil), // 47: openshell.v1.SandboxWorkloadTemplateProvenance - (*SandboxStatus)(nil), // 48: openshell.v1.SandboxStatus - (*SandboxCondition)(nil), // 49: openshell.v1.SandboxCondition - (*PlatformEvent)(nil), // 50: openshell.v1.PlatformEvent - (*CreateSandboxRequest)(nil), // 51: openshell.v1.CreateSandboxRequest - (*CreateSandboxTemplateRequest)(nil), // 52: openshell.v1.CreateSandboxTemplateRequest - (*GetSandboxTemplateRequest)(nil), // 53: openshell.v1.GetSandboxTemplateRequest - (*ListSandboxTemplatesRequest)(nil), // 54: openshell.v1.ListSandboxTemplatesRequest - (*DeleteSandboxTemplateRequest)(nil), // 55: openshell.v1.DeleteSandboxTemplateRequest - (*SandboxTemplateResponse)(nil), // 56: openshell.v1.SandboxTemplateResponse - (*ListSandboxTemplatesResponse)(nil), // 57: openshell.v1.ListSandboxTemplatesResponse - (*DeleteSandboxTemplateResponse)(nil), // 58: openshell.v1.DeleteSandboxTemplateResponse - (*BeginRootfsTarStagingRequest)(nil), // 59: openshell.v1.BeginRootfsTarStagingRequest - (*BeginRootfsTarStagingResponse)(nil), // 60: openshell.v1.BeginRootfsTarStagingResponse - (*GetSandboxRequest)(nil), // 61: openshell.v1.GetSandboxRequest - (*ListSandboxesRequest)(nil), // 62: openshell.v1.ListSandboxesRequest - (*ListSandboxProvidersRequest)(nil), // 63: openshell.v1.ListSandboxProvidersRequest - (*AttachSandboxProviderRequest)(nil), // 64: openshell.v1.AttachSandboxProviderRequest - (*DetachSandboxProviderRequest)(nil), // 65: openshell.v1.DetachSandboxProviderRequest - (*DeleteSandboxRequest)(nil), // 66: openshell.v1.DeleteSandboxRequest - (*StopSandboxRequest)(nil), // 67: openshell.v1.StopSandboxRequest - (*StartSandboxRequest)(nil), // 68: openshell.v1.StartSandboxRequest - (*SandboxResponse)(nil), // 69: openshell.v1.SandboxResponse - (*ListSandboxesResponse)(nil), // 70: openshell.v1.ListSandboxesResponse - (*ListSandboxProvidersResponse)(nil), // 71: openshell.v1.ListSandboxProvidersResponse - (*AttachSandboxProviderResponse)(nil), // 72: openshell.v1.AttachSandboxProviderResponse - (*DetachSandboxProviderResponse)(nil), // 73: openshell.v1.DetachSandboxProviderResponse - (*ProviderDesiredIdentity)(nil), // 74: openshell.v1.ProviderDesiredIdentity - (*ConfigSnapshotRevision)(nil), // 75: openshell.v1.ConfigSnapshotRevision - (*SandboxConfigRevision)(nil), // 76: openshell.v1.SandboxConfigRevision - (*ConfigUpdateOperation)(nil), // 77: openshell.v1.ConfigUpdateOperation - (*ProviderMutationReceipt)(nil), // 78: openshell.v1.ProviderMutationReceipt - (*ProviderReadinessObservation)(nil), // 79: openshell.v1.ProviderReadinessObservation - (*ProviderReadinessStatus)(nil), // 80: openshell.v1.ProviderReadinessStatus - (*GetSandboxProviderStatusRequest)(nil), // 81: openshell.v1.GetSandboxProviderStatusRequest - (*GetSandboxProviderStatusResponse)(nil), // 82: openshell.v1.GetSandboxProviderStatusResponse - (*ReportProviderReadinessRequest)(nil), // 83: openshell.v1.ReportProviderReadinessRequest - (*ReportProviderReadinessResponse)(nil), // 84: openshell.v1.ReportProviderReadinessResponse - (*DeleteSandboxResponse)(nil), // 85: openshell.v1.DeleteSandboxResponse - (*CreateSshSessionRequest)(nil), // 86: openshell.v1.CreateSshSessionRequest - (*CreateSshSessionResponse)(nil), // 87: openshell.v1.CreateSshSessionResponse - (*ExposeServiceRequest)(nil), // 88: openshell.v1.ExposeServiceRequest - (*GetServiceRequest)(nil), // 89: openshell.v1.GetServiceRequest - (*ListServicesRequest)(nil), // 90: openshell.v1.ListServicesRequest - (*ListServicesResponse)(nil), // 91: openshell.v1.ListServicesResponse - (*DeleteServiceRequest)(nil), // 92: openshell.v1.DeleteServiceRequest - (*DeleteServiceResponse)(nil), // 93: openshell.v1.DeleteServiceResponse - (*ServiceEndpoint)(nil), // 94: openshell.v1.ServiceEndpoint - (*ServiceEndpointResponse)(nil), // 95: openshell.v1.ServiceEndpointResponse - (*RevokeSshSessionRequest)(nil), // 96: openshell.v1.RevokeSshSessionRequest - (*RevokeSshSessionResponse)(nil), // 97: openshell.v1.RevokeSshSessionResponse - (*ExecSandboxRequest)(nil), // 98: openshell.v1.ExecSandboxRequest - (*ExecSandboxStdout)(nil), // 99: openshell.v1.ExecSandboxStdout - (*ExecSandboxStderr)(nil), // 100: openshell.v1.ExecSandboxStderr - (*ExecSandboxExit)(nil), // 101: openshell.v1.ExecSandboxExit - (*ExecSandboxEvent)(nil), // 102: openshell.v1.ExecSandboxEvent - (*TcpForwardInit)(nil), // 103: openshell.v1.TcpForwardInit - (*TcpForwardFrame)(nil), // 104: openshell.v1.TcpForwardFrame - (*ExecSandboxInput)(nil), // 105: openshell.v1.ExecSandboxInput - (*ExecSandboxWindowResize)(nil), // 106: openshell.v1.ExecSandboxWindowResize - (*SshSession)(nil), // 107: openshell.v1.SshSession - (*WatchSandboxRequest)(nil), // 108: openshell.v1.WatchSandboxRequest - (*SandboxStreamEvent)(nil), // 109: openshell.v1.SandboxStreamEvent - (*SandboxLogLine)(nil), // 110: openshell.v1.SandboxLogLine - (*SandboxStreamWarning)(nil), // 111: openshell.v1.SandboxStreamWarning - (*CreateProviderRequest)(nil), // 112: openshell.v1.CreateProviderRequest - (*GetProviderRequest)(nil), // 113: openshell.v1.GetProviderRequest - (*ListProvidersRequest)(nil), // 114: openshell.v1.ListProvidersRequest - (*UpdateProviderRequest)(nil), // 115: openshell.v1.UpdateProviderRequest - (*DeleteProviderRequest)(nil), // 116: openshell.v1.DeleteProviderRequest - (*ProviderResponse)(nil), // 117: openshell.v1.ProviderResponse - (*ListProvidersResponse)(nil), // 118: openshell.v1.ListProvidersResponse - (*ListProviderProfilesRequest)(nil), // 119: openshell.v1.ListProviderProfilesRequest - (*GetProviderProfileRequest)(nil), // 120: openshell.v1.GetProviderProfileRequest - (*ProviderProfileImportItem)(nil), // 121: openshell.v1.ProviderProfileImportItem - (*ProviderProfileDiagnostic)(nil), // 122: openshell.v1.ProviderProfileDiagnostic - (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 123: openshell.v1.ProviderCredentialTokenGrantAudienceOverride - (*ProviderCredentialTokenGrantSubjectToken)(nil), // 124: openshell.v1.ProviderCredentialTokenGrantSubjectToken - (*ProviderCredentialTokenGrant)(nil), // 125: openshell.v1.ProviderCredentialTokenGrant - (*ProviderProfileCredential)(nil), // 126: openshell.v1.ProviderProfileCredential - (*ProviderCredentialRefreshMaterial)(nil), // 127: openshell.v1.ProviderCredentialRefreshMaterial - (*ProviderCredentialRefreshOutput)(nil), // 128: openshell.v1.ProviderCredentialRefreshOutput - (*ProviderCredentialRefresh)(nil), // 129: openshell.v1.ProviderCredentialRefresh - (*ProviderCredentialRefreshStatus)(nil), // 130: openshell.v1.ProviderCredentialRefreshStatus - (*ProviderProfileDiscovery)(nil), // 131: openshell.v1.ProviderProfileDiscovery - (*GetProviderRefreshStatusRequest)(nil), // 132: openshell.v1.GetProviderRefreshStatusRequest - (*GetProviderRefreshStatusResponse)(nil), // 133: openshell.v1.GetProviderRefreshStatusResponse - (*ConfigureProviderRefreshRequest)(nil), // 134: openshell.v1.ConfigureProviderRefreshRequest - (*ConfigureProviderRefreshResponse)(nil), // 135: openshell.v1.ConfigureProviderRefreshResponse - (*RotateProviderCredentialRequest)(nil), // 136: openshell.v1.RotateProviderCredentialRequest - (*RotateProviderCredentialResponse)(nil), // 137: openshell.v1.RotateProviderCredentialResponse - (*DeleteProviderRefreshRequest)(nil), // 138: openshell.v1.DeleteProviderRefreshRequest - (*DeleteProviderRefreshResponse)(nil), // 139: openshell.v1.DeleteProviderRefreshResponse - (*ProviderProfile)(nil), // 140: openshell.v1.ProviderProfile - (*ProviderProfileFile)(nil), // 141: openshell.v1.ProviderProfileFile - (*ProviderProfileResponse)(nil), // 142: openshell.v1.ProviderProfileResponse - (*ListProviderProfilesResponse)(nil), // 143: openshell.v1.ListProviderProfilesResponse - (*ImportProviderProfilesRequest)(nil), // 144: openshell.v1.ImportProviderProfilesRequest - (*ImportProviderProfilesResponse)(nil), // 145: openshell.v1.ImportProviderProfilesResponse - (*UpdateProviderProfilesRequest)(nil), // 146: openshell.v1.UpdateProviderProfilesRequest - (*UpdateProviderProfilesResponse)(nil), // 147: openshell.v1.UpdateProviderProfilesResponse - (*LintProviderProfilesRequest)(nil), // 148: openshell.v1.LintProviderProfilesRequest - (*LintProviderProfilesResponse)(nil), // 149: openshell.v1.LintProviderProfilesResponse - (*DeleteProviderResponse)(nil), // 150: openshell.v1.DeleteProviderResponse - (*DeleteProviderProfileRequest)(nil), // 151: openshell.v1.DeleteProviderProfileRequest - (*DeleteProviderProfileResponse)(nil), // 152: openshell.v1.DeleteProviderProfileResponse - (*GetSandboxProviderEnvironmentRequest)(nil), // 153: openshell.v1.GetSandboxProviderEnvironmentRequest - (*StaticCredentialEndpointBinding)(nil), // 154: openshell.v1.StaticCredentialEndpointBinding - (*StaticCredentialBinding)(nil), // 155: openshell.v1.StaticCredentialBinding - (*GetSandboxProviderEnvironmentResponse)(nil), // 156: openshell.v1.GetSandboxProviderEnvironmentResponse - (*ExchangeProviderSubjectTokenRequest)(nil), // 157: openshell.v1.ExchangeProviderSubjectTokenRequest - (*ExchangeProviderSubjectTokenResponse)(nil), // 158: openshell.v1.ExchangeProviderSubjectTokenResponse - (*UpdateConfigRequest)(nil), // 159: openshell.v1.UpdateConfigRequest - (*PolicyMergeOperation)(nil), // 160: openshell.v1.PolicyMergeOperation - (*AddNetworkRule)(nil), // 161: openshell.v1.AddNetworkRule - (*RemoveNetworkEndpoint)(nil), // 162: openshell.v1.RemoveNetworkEndpoint - (*RemoveNetworkRule)(nil), // 163: openshell.v1.RemoveNetworkRule - (*L7RuleTarget)(nil), // 164: openshell.v1.L7RuleTarget - (*AddDenyRules)(nil), // 165: openshell.v1.AddDenyRules - (*AddAllowRules)(nil), // 166: openshell.v1.AddAllowRules - (*RemoveNetworkBinary)(nil), // 167: openshell.v1.RemoveNetworkBinary - (*UpdateConfigResponse)(nil), // 168: openshell.v1.UpdateConfigResponse - (*GetSandboxPolicyStatusRequest)(nil), // 169: openshell.v1.GetSandboxPolicyStatusRequest - (*GetSandboxPolicyStatusResponse)(nil), // 170: openshell.v1.GetSandboxPolicyStatusResponse - (*ListSandboxPoliciesRequest)(nil), // 171: openshell.v1.ListSandboxPoliciesRequest - (*ListSandboxPoliciesResponse)(nil), // 172: openshell.v1.ListSandboxPoliciesResponse - (*ReportPolicyStatusRequest)(nil), // 173: openshell.v1.ReportPolicyStatusRequest - (*ReportPolicyStatusResponse)(nil), // 174: openshell.v1.ReportPolicyStatusResponse - (*SandboxConfigurationAdmission)(nil), // 175: openshell.v1.SandboxConfigurationAdmission - (*ReportSandboxConfigurationRequest)(nil), // 176: openshell.v1.ReportSandboxConfigurationRequest - (*ReportSandboxConfigurationResponse)(nil), // 177: openshell.v1.ReportSandboxConfigurationResponse - (*SandboxPolicyRevision)(nil), // 178: openshell.v1.SandboxPolicyRevision - (*GetSandboxLogsRequest)(nil), // 179: openshell.v1.GetSandboxLogsRequest - (*PushSandboxLogsRequest)(nil), // 180: openshell.v1.PushSandboxLogsRequest - (*PushSandboxLogsResponse)(nil), // 181: openshell.v1.PushSandboxLogsResponse - (*GetSandboxLogsResponse)(nil), // 182: openshell.v1.GetSandboxLogsResponse - (*SupervisorMessage)(nil), // 183: openshell.v1.SupervisorMessage - (*GatewayMessage)(nil), // 184: openshell.v1.GatewayMessage - (*SupervisorHello)(nil), // 185: openshell.v1.SupervisorHello - (*SessionAccepted)(nil), // 186: openshell.v1.SessionAccepted - (*SessionRejected)(nil), // 187: openshell.v1.SessionRejected - (*SupervisorHeartbeat)(nil), // 188: openshell.v1.SupervisorHeartbeat - (*GatewayHeartbeat)(nil), // 189: openshell.v1.GatewayHeartbeat - (*ReportMainProcessExitRequest)(nil), // 190: openshell.v1.ReportMainProcessExitRequest - (*ReportMainProcessExitResponse)(nil), // 191: openshell.v1.ReportMainProcessExitResponse - (*FinalizeMainProcessExitRequest)(nil), // 192: openshell.v1.FinalizeMainProcessExitRequest - (*FinalizeMainProcessExitResponse)(nil), // 193: openshell.v1.FinalizeMainProcessExitResponse - (*RelayOpen)(nil), // 194: openshell.v1.RelayOpen - (*SshRelayTarget)(nil), // 195: openshell.v1.SshRelayTarget - (*TcpRelayTarget)(nil), // 196: openshell.v1.TcpRelayTarget - (*RelayInit)(nil), // 197: openshell.v1.RelayInit - (*RelayFrame)(nil), // 198: openshell.v1.RelayFrame - (*PeerRelayInit)(nil), // 199: openshell.v1.PeerRelayInit - (*PeerRelayFrame)(nil), // 200: openshell.v1.PeerRelayFrame - (*RelayOpenResult)(nil), // 201: openshell.v1.RelayOpenResult - (*RelayClose)(nil), // 202: openshell.v1.RelayClose - (*L7RequestSample)(nil), // 203: openshell.v1.L7RequestSample - (*DenialSummary)(nil), // 204: openshell.v1.DenialSummary - (*DenialGroupCount)(nil), // 205: openshell.v1.DenialGroupCount - (*NetworkActivitySummary)(nil), // 206: openshell.v1.NetworkActivitySummary - (*PolicyChunk)(nil), // 207: openshell.v1.PolicyChunk - (*DraftPolicyUpdate)(nil), // 208: openshell.v1.DraftPolicyUpdate - (*SubmitPolicyAnalysisRequest)(nil), // 209: openshell.v1.SubmitPolicyAnalysisRequest - (*SubmitPolicyAnalysisResponse)(nil), // 210: openshell.v1.SubmitPolicyAnalysisResponse - (*GetDraftPolicyRequest)(nil), // 211: openshell.v1.GetDraftPolicyRequest - (*GetDraftPolicyResponse)(nil), // 212: openshell.v1.GetDraftPolicyResponse - (*ApproveDraftChunkRequest)(nil), // 213: openshell.v1.ApproveDraftChunkRequest - (*ApproveDraftChunkResponse)(nil), // 214: openshell.v1.ApproveDraftChunkResponse - (*RejectDraftChunkRequest)(nil), // 215: openshell.v1.RejectDraftChunkRequest - (*RejectDraftChunkResponse)(nil), // 216: openshell.v1.RejectDraftChunkResponse - (*DraftChunkApproval)(nil), // 217: openshell.v1.DraftChunkApproval - (*ApproveAllDraftChunksRequest)(nil), // 218: openshell.v1.ApproveAllDraftChunksRequest - (*ApproveAllDraftChunksResponse)(nil), // 219: openshell.v1.ApproveAllDraftChunksResponse - (*EditDraftChunkRequest)(nil), // 220: openshell.v1.EditDraftChunkRequest - (*EditDraftChunkResponse)(nil), // 221: openshell.v1.EditDraftChunkResponse - (*UndoDraftChunkRequest)(nil), // 222: openshell.v1.UndoDraftChunkRequest - (*UndoDraftChunkResponse)(nil), // 223: openshell.v1.UndoDraftChunkResponse - (*ClearDraftChunksRequest)(nil), // 224: openshell.v1.ClearDraftChunksRequest - (*ClearDraftChunksResponse)(nil), // 225: openshell.v1.ClearDraftChunksResponse - (*GetDraftHistoryRequest)(nil), // 226: openshell.v1.GetDraftHistoryRequest - (*DraftHistoryEntry)(nil), // 227: openshell.v1.DraftHistoryEntry - (*GetDraftHistoryResponse)(nil), // 228: openshell.v1.GetDraftHistoryResponse - (*CreateWorkspaceRequest)(nil), // 229: openshell.v1.CreateWorkspaceRequest - (*CreateWorkspaceResponse)(nil), // 230: openshell.v1.CreateWorkspaceResponse - (*GetWorkspaceRequest)(nil), // 231: openshell.v1.GetWorkspaceRequest - (*GetWorkspaceResponse)(nil), // 232: openshell.v1.GetWorkspaceResponse - (*ListWorkspacesRequest)(nil), // 233: openshell.v1.ListWorkspacesRequest - (*ListWorkspacesResponse)(nil), // 234: openshell.v1.ListWorkspacesResponse - (*DeleteWorkspaceRequest)(nil), // 235: openshell.v1.DeleteWorkspaceRequest - (*DeleteWorkspaceResponse)(nil), // 236: openshell.v1.DeleteWorkspaceResponse - (*WorkspaceMember)(nil), // 237: openshell.v1.WorkspaceMember - (*AddWorkspaceMemberRequest)(nil), // 238: openshell.v1.AddWorkspaceMemberRequest - (*AddWorkspaceMemberResponse)(nil), // 239: openshell.v1.AddWorkspaceMemberResponse - (*RemoveWorkspaceMemberRequest)(nil), // 240: openshell.v1.RemoveWorkspaceMemberRequest - (*RemoveWorkspaceMemberResponse)(nil), // 241: openshell.v1.RemoveWorkspaceMemberResponse - (*ListWorkspaceMembersRequest)(nil), // 242: openshell.v1.ListWorkspaceMembersRequest - (*ListWorkspaceMembersResponse)(nil), // 243: openshell.v1.ListWorkspaceMembersResponse - (*ExtensionServiceCredential)(nil), // 244: openshell.v1.ExtensionServiceCredential - (*EndpointObservation)(nil), // 245: openshell.v1.EndpointObservation - (*ReportEndpointStatusRequest)(nil), // 246: openshell.v1.ReportEndpointStatusRequest - (*ReportEndpointStatusResponse)(nil), // 247: openshell.v1.ReportEndpointStatusResponse - (*EndpointStatus)(nil), // 248: openshell.v1.EndpointStatus - (*SandboxProvisioning)(nil), // 249: openshell.v1.SandboxProvisioning - (*SandboxServiceExposure)(nil), // 250: openshell.v1.SandboxServiceExposure - nil, // 251: openshell.v1.SandboxSpec.EnvironmentEntry - nil, // 252: openshell.v1.SandboxTemplate.LabelsEntry - nil, // 253: openshell.v1.SandboxTemplate.AnnotationsEntry - nil, // 254: openshell.v1.SandboxTemplate.EnvironmentEntry - nil, // 255: openshell.v1.SandboxWorkloadConfig.EnvironmentEntry - nil, // 256: openshell.v1.PlatformEvent.MetadataEntry - nil, // 257: openshell.v1.CreateSandboxRequest.LabelsEntry - nil, // 258: openshell.v1.CreateSandboxRequest.AnnotationsEntry - nil, // 259: openshell.v1.SandboxResponse.ServiceUrlsEntry - nil, // 260: openshell.v1.ExecSandboxRequest.EnvironmentEntry - nil, // 261: openshell.v1.SandboxLogLine.FieldsEntry - nil, // 262: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry - nil, // 263: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - nil, // 264: openshell.v1.ProviderProfile.AnnotationsEntry - nil, // 265: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - nil, // 266: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry - nil, // 267: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - nil, // 268: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - nil, // 269: openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry - nil, // 270: openshell.v1.UpdateConfigRequest.AnnotationsEntry - nil, // 271: openshell.v1.UpdateConfigResponse.AnnotationsEntry - nil, // 272: openshell.v1.SandboxPolicyRevision.ProvenanceEntry - nil, // 273: openshell.v1.CreateWorkspaceRequest.LabelsEntry - (*timestamppb.Timestamp)(nil), // 274: google.protobuf.Timestamp - (*datamodelv1.ObjectMeta)(nil), // 275: openshell.datamodel.v1.ObjectMeta - (*sandboxv1.SandboxPolicy)(nil), // 276: openshell.sandbox.v1.SandboxPolicy - (*structpb.Struct)(nil), // 277: google.protobuf.Struct - (*durationpb.Duration)(nil), // 278: google.protobuf.Duration - (*datamodelv1.WorkspaceSelector)(nil), // 279: openshell.datamodel.v1.WorkspaceSelector - (*datamodelv1.Provider)(nil), // 280: openshell.datamodel.v1.Provider - (sandboxv1.PolicySource)(0), // 281: openshell.sandbox.v1.PolicySource - (*sandboxv1.NetworkEndpoint)(nil), // 282: openshell.sandbox.v1.NetworkEndpoint - (*sandboxv1.NetworkBinary)(nil), // 283: openshell.sandbox.v1.NetworkBinary - (*sandboxv1.SettingValue)(nil), // 284: openshell.sandbox.v1.SettingValue - (*sandboxv1.NetworkPolicyRule)(nil), // 285: openshell.sandbox.v1.NetworkPolicyRule - (*sandboxv1.L7DenyRule)(nil), // 286: openshell.sandbox.v1.L7DenyRule - (*sandboxv1.L7Rule)(nil), // 287: openshell.sandbox.v1.L7Rule - (*datamodelv1.Workspace)(nil), // 288: openshell.datamodel.v1.Workspace - (*sandboxv1.GetSandboxConfigRequest)(nil), // 289: openshell.sandbox.v1.GetSandboxConfigRequest - (*sandboxv1.GetGatewayConfigRequest)(nil), // 290: openshell.sandbox.v1.GetGatewayConfigRequest - (*sandboxv1.GetSandboxConfigResponse)(nil), // 291: openshell.sandbox.v1.GetSandboxConfigResponse - (*sandboxv1.GetGatewayConfigResponse)(nil), // 292: openshell.sandbox.v1.GetGatewayConfigResponse + (ServiceAuthorizationMode)(0), // 19: openshell.v1.ServiceAuthorizationMode + (*IssueSandboxTokenRequest)(nil), // 20: openshell.v1.IssueSandboxTokenRequest + (*IssueSandboxTokenResponse)(nil), // 21: openshell.v1.IssueSandboxTokenResponse + (*RefreshSandboxTokenRequest)(nil), // 22: openshell.v1.RefreshSandboxTokenRequest + (*RefreshSandboxTokenResponse)(nil), // 23: openshell.v1.RefreshSandboxTokenResponse + (*HealthRequest)(nil), // 24: openshell.v1.HealthRequest + (*HealthResponse)(nil), // 25: openshell.v1.HealthResponse + (*GetCurrentUserRequest)(nil), // 26: openshell.v1.GetCurrentUserRequest + (*GetCurrentUserResponse)(nil), // 27: openshell.v1.GetCurrentUserResponse + (*GetGatewayInfoRequest)(nil), // 28: openshell.v1.GetGatewayInfoRequest + (*GetGatewayInfoResponse)(nil), // 29: openshell.v1.GetGatewayInfoResponse + (*NegotiatedExtensionInfo)(nil), // 30: openshell.v1.NegotiatedExtensionInfo + (*ComputeDriverInfo)(nil), // 31: openshell.v1.ComputeDriverInfo + (*ComputeDriverCapabilities)(nil), // 32: openshell.v1.ComputeDriverCapabilities + (*ResourceCapabilities)(nil), // 33: openshell.v1.ResourceCapabilities + (*CpuResourceCapabilities)(nil), // 34: openshell.v1.CpuResourceCapabilities + (*MemoryResourceCapabilities)(nil), // 35: openshell.v1.MemoryResourceCapabilities + (*GpuResourceCapabilities)(nil), // 36: openshell.v1.GpuResourceCapabilities + (*Sandbox)(nil), // 37: openshell.v1.Sandbox + (*SandboxSpec)(nil), // 38: openshell.v1.SandboxSpec + (*ResourceRequirements)(nil), // 39: openshell.v1.ResourceRequirements + (*GpuResourceRequirements)(nil), // 40: openshell.v1.GpuResourceRequirements + (*SandboxTemplate)(nil), // 41: openshell.v1.SandboxTemplate + (*SandboxWorkloadTemplate)(nil), // 42: openshell.v1.SandboxWorkloadTemplate + (*SandboxWorkloadTemplateSpec)(nil), // 43: openshell.v1.SandboxWorkloadTemplateSpec + (*SandboxWorkloadConfig)(nil), // 44: openshell.v1.SandboxWorkloadConfig + (*SandboxResources)(nil), // 45: openshell.v1.SandboxResources + (*SandboxServiceLevel)(nil), // 46: openshell.v1.SandboxServiceLevel + (*SandboxStartup)(nil), // 47: openshell.v1.SandboxStartup + (*SandboxWorkloadTemplateProvenance)(nil), // 48: openshell.v1.SandboxWorkloadTemplateProvenance + (*SandboxStatus)(nil), // 49: openshell.v1.SandboxStatus + (*SandboxCondition)(nil), // 50: openshell.v1.SandboxCondition + (*PlatformEvent)(nil), // 51: openshell.v1.PlatformEvent + (*CreateSandboxRequest)(nil), // 52: openshell.v1.CreateSandboxRequest + (*CreateSandboxTemplateRequest)(nil), // 53: openshell.v1.CreateSandboxTemplateRequest + (*GetSandboxTemplateRequest)(nil), // 54: openshell.v1.GetSandboxTemplateRequest + (*ListSandboxTemplatesRequest)(nil), // 55: openshell.v1.ListSandboxTemplatesRequest + (*DeleteSandboxTemplateRequest)(nil), // 56: openshell.v1.DeleteSandboxTemplateRequest + (*SandboxTemplateResponse)(nil), // 57: openshell.v1.SandboxTemplateResponse + (*ListSandboxTemplatesResponse)(nil), // 58: openshell.v1.ListSandboxTemplatesResponse + (*DeleteSandboxTemplateResponse)(nil), // 59: openshell.v1.DeleteSandboxTemplateResponse + (*BeginRootfsTarStagingRequest)(nil), // 60: openshell.v1.BeginRootfsTarStagingRequest + (*BeginRootfsTarStagingResponse)(nil), // 61: openshell.v1.BeginRootfsTarStagingResponse + (*GetSandboxRequest)(nil), // 62: openshell.v1.GetSandboxRequest + (*ListSandboxesRequest)(nil), // 63: openshell.v1.ListSandboxesRequest + (*ListSandboxProvidersRequest)(nil), // 64: openshell.v1.ListSandboxProvidersRequest + (*AttachSandboxProviderRequest)(nil), // 65: openshell.v1.AttachSandboxProviderRequest + (*DetachSandboxProviderRequest)(nil), // 66: openshell.v1.DetachSandboxProviderRequest + (*DeleteSandboxRequest)(nil), // 67: openshell.v1.DeleteSandboxRequest + (*StopSandboxRequest)(nil), // 68: openshell.v1.StopSandboxRequest + (*StartSandboxRequest)(nil), // 69: openshell.v1.StartSandboxRequest + (*SandboxResponse)(nil), // 70: openshell.v1.SandboxResponse + (*ListSandboxesResponse)(nil), // 71: openshell.v1.ListSandboxesResponse + (*ListSandboxProvidersResponse)(nil), // 72: openshell.v1.ListSandboxProvidersResponse + (*AttachSandboxProviderResponse)(nil), // 73: openshell.v1.AttachSandboxProviderResponse + (*DetachSandboxProviderResponse)(nil), // 74: openshell.v1.DetachSandboxProviderResponse + (*ProviderDesiredIdentity)(nil), // 75: openshell.v1.ProviderDesiredIdentity + (*ConfigSnapshotRevision)(nil), // 76: openshell.v1.ConfigSnapshotRevision + (*SandboxConfigRevision)(nil), // 77: openshell.v1.SandboxConfigRevision + (*ConfigUpdateOperation)(nil), // 78: openshell.v1.ConfigUpdateOperation + (*ProviderMutationReceipt)(nil), // 79: openshell.v1.ProviderMutationReceipt + (*ProviderReadinessObservation)(nil), // 80: openshell.v1.ProviderReadinessObservation + (*ProviderReadinessStatus)(nil), // 81: openshell.v1.ProviderReadinessStatus + (*GetSandboxProviderStatusRequest)(nil), // 82: openshell.v1.GetSandboxProviderStatusRequest + (*GetSandboxProviderStatusResponse)(nil), // 83: openshell.v1.GetSandboxProviderStatusResponse + (*ReportProviderReadinessRequest)(nil), // 84: openshell.v1.ReportProviderReadinessRequest + (*ReportProviderReadinessResponse)(nil), // 85: openshell.v1.ReportProviderReadinessResponse + (*DeleteSandboxResponse)(nil), // 86: openshell.v1.DeleteSandboxResponse + (*CreateSshSessionRequest)(nil), // 87: openshell.v1.CreateSshSessionRequest + (*CreateSshSessionResponse)(nil), // 88: openshell.v1.CreateSshSessionResponse + (*ExposeServiceRequest)(nil), // 89: openshell.v1.ExposeServiceRequest + (*GetServiceRequest)(nil), // 90: openshell.v1.GetServiceRequest + (*ListServicesRequest)(nil), // 91: openshell.v1.ListServicesRequest + (*ListServicesResponse)(nil), // 92: openshell.v1.ListServicesResponse + (*DeleteServiceRequest)(nil), // 93: openshell.v1.DeleteServiceRequest + (*DeleteServiceResponse)(nil), // 94: openshell.v1.DeleteServiceResponse + (*ServiceEndpoint)(nil), // 95: openshell.v1.ServiceEndpoint + (*ServiceEndpointResponse)(nil), // 96: openshell.v1.ServiceEndpointResponse + (*RevokeSshSessionRequest)(nil), // 97: openshell.v1.RevokeSshSessionRequest + (*RevokeSshSessionResponse)(nil), // 98: openshell.v1.RevokeSshSessionResponse + (*ExecSandboxRequest)(nil), // 99: openshell.v1.ExecSandboxRequest + (*ExecSandboxStdout)(nil), // 100: openshell.v1.ExecSandboxStdout + (*ExecSandboxStderr)(nil), // 101: openshell.v1.ExecSandboxStderr + (*ExecSandboxExit)(nil), // 102: openshell.v1.ExecSandboxExit + (*ExecSandboxEvent)(nil), // 103: openshell.v1.ExecSandboxEvent + (*TcpForwardInit)(nil), // 104: openshell.v1.TcpForwardInit + (*TcpForwardFrame)(nil), // 105: openshell.v1.TcpForwardFrame + (*ExecSandboxInput)(nil), // 106: openshell.v1.ExecSandboxInput + (*ExecSandboxWindowResize)(nil), // 107: openshell.v1.ExecSandboxWindowResize + (*SshSession)(nil), // 108: openshell.v1.SshSession + (*WatchSandboxRequest)(nil), // 109: openshell.v1.WatchSandboxRequest + (*SandboxStreamEvent)(nil), // 110: openshell.v1.SandboxStreamEvent + (*SandboxLogLine)(nil), // 111: openshell.v1.SandboxLogLine + (*SandboxStreamWarning)(nil), // 112: openshell.v1.SandboxStreamWarning + (*CreateProviderRequest)(nil), // 113: openshell.v1.CreateProviderRequest + (*GetProviderRequest)(nil), // 114: openshell.v1.GetProviderRequest + (*ListProvidersRequest)(nil), // 115: openshell.v1.ListProvidersRequest + (*UpdateProviderRequest)(nil), // 116: openshell.v1.UpdateProviderRequest + (*DeleteProviderRequest)(nil), // 117: openshell.v1.DeleteProviderRequest + (*ProviderResponse)(nil), // 118: openshell.v1.ProviderResponse + (*ListProvidersResponse)(nil), // 119: openshell.v1.ListProvidersResponse + (*ListProviderProfilesRequest)(nil), // 120: openshell.v1.ListProviderProfilesRequest + (*GetProviderProfileRequest)(nil), // 121: openshell.v1.GetProviderProfileRequest + (*ProviderProfileImportItem)(nil), // 122: openshell.v1.ProviderProfileImportItem + (*ProviderProfileDiagnostic)(nil), // 123: openshell.v1.ProviderProfileDiagnostic + (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 124: openshell.v1.ProviderCredentialTokenGrantAudienceOverride + (*ProviderCredentialTokenGrantSubjectToken)(nil), // 125: openshell.v1.ProviderCredentialTokenGrantSubjectToken + (*ProviderCredentialTokenGrant)(nil), // 126: openshell.v1.ProviderCredentialTokenGrant + (*ProviderProfileCredential)(nil), // 127: openshell.v1.ProviderProfileCredential + (*ProviderCredentialRefreshMaterial)(nil), // 128: openshell.v1.ProviderCredentialRefreshMaterial + (*ProviderCredentialRefreshOutput)(nil), // 129: openshell.v1.ProviderCredentialRefreshOutput + (*ProviderCredentialRefresh)(nil), // 130: openshell.v1.ProviderCredentialRefresh + (*ProviderCredentialRefreshStatus)(nil), // 131: openshell.v1.ProviderCredentialRefreshStatus + (*ProviderProfileDiscovery)(nil), // 132: openshell.v1.ProviderProfileDiscovery + (*GetProviderRefreshStatusRequest)(nil), // 133: openshell.v1.GetProviderRefreshStatusRequest + (*GetProviderRefreshStatusResponse)(nil), // 134: openshell.v1.GetProviderRefreshStatusResponse + (*ConfigureProviderRefreshRequest)(nil), // 135: openshell.v1.ConfigureProviderRefreshRequest + (*ConfigureProviderRefreshResponse)(nil), // 136: openshell.v1.ConfigureProviderRefreshResponse + (*RotateProviderCredentialRequest)(nil), // 137: openshell.v1.RotateProviderCredentialRequest + (*RotateProviderCredentialResponse)(nil), // 138: openshell.v1.RotateProviderCredentialResponse + (*DeleteProviderRefreshRequest)(nil), // 139: openshell.v1.DeleteProviderRefreshRequest + (*DeleteProviderRefreshResponse)(nil), // 140: openshell.v1.DeleteProviderRefreshResponse + (*ProviderProfile)(nil), // 141: openshell.v1.ProviderProfile + (*ProviderProfileFile)(nil), // 142: openshell.v1.ProviderProfileFile + (*ProviderProfileResponse)(nil), // 143: openshell.v1.ProviderProfileResponse + (*ListProviderProfilesResponse)(nil), // 144: openshell.v1.ListProviderProfilesResponse + (*ImportProviderProfilesRequest)(nil), // 145: openshell.v1.ImportProviderProfilesRequest + (*ImportProviderProfilesResponse)(nil), // 146: openshell.v1.ImportProviderProfilesResponse + (*UpdateProviderProfilesRequest)(nil), // 147: openshell.v1.UpdateProviderProfilesRequest + (*UpdateProviderProfilesResponse)(nil), // 148: openshell.v1.UpdateProviderProfilesResponse + (*LintProviderProfilesRequest)(nil), // 149: openshell.v1.LintProviderProfilesRequest + (*LintProviderProfilesResponse)(nil), // 150: openshell.v1.LintProviderProfilesResponse + (*DeleteProviderResponse)(nil), // 151: openshell.v1.DeleteProviderResponse + (*DeleteProviderProfileRequest)(nil), // 152: openshell.v1.DeleteProviderProfileRequest + (*DeleteProviderProfileResponse)(nil), // 153: openshell.v1.DeleteProviderProfileResponse + (*GetSandboxProviderEnvironmentRequest)(nil), // 154: openshell.v1.GetSandboxProviderEnvironmentRequest + (*StaticCredentialEndpointBinding)(nil), // 155: openshell.v1.StaticCredentialEndpointBinding + (*StaticCredentialBinding)(nil), // 156: openshell.v1.StaticCredentialBinding + (*GetSandboxProviderEnvironmentResponse)(nil), // 157: openshell.v1.GetSandboxProviderEnvironmentResponse + (*ExchangeProviderSubjectTokenRequest)(nil), // 158: openshell.v1.ExchangeProviderSubjectTokenRequest + (*ExchangeProviderSubjectTokenResponse)(nil), // 159: openshell.v1.ExchangeProviderSubjectTokenResponse + (*UpdateConfigRequest)(nil), // 160: openshell.v1.UpdateConfigRequest + (*PolicyMergeOperation)(nil), // 161: openshell.v1.PolicyMergeOperation + (*AddNetworkRule)(nil), // 162: openshell.v1.AddNetworkRule + (*RemoveNetworkEndpoint)(nil), // 163: openshell.v1.RemoveNetworkEndpoint + (*RemoveNetworkRule)(nil), // 164: openshell.v1.RemoveNetworkRule + (*L7RuleTarget)(nil), // 165: openshell.v1.L7RuleTarget + (*AddDenyRules)(nil), // 166: openshell.v1.AddDenyRules + (*AddAllowRules)(nil), // 167: openshell.v1.AddAllowRules + (*RemoveNetworkBinary)(nil), // 168: openshell.v1.RemoveNetworkBinary + (*UpdateConfigResponse)(nil), // 169: openshell.v1.UpdateConfigResponse + (*GetSandboxPolicyStatusRequest)(nil), // 170: openshell.v1.GetSandboxPolicyStatusRequest + (*GetSandboxPolicyStatusResponse)(nil), // 171: openshell.v1.GetSandboxPolicyStatusResponse + (*ListSandboxPoliciesRequest)(nil), // 172: openshell.v1.ListSandboxPoliciesRequest + (*ListSandboxPoliciesResponse)(nil), // 173: openshell.v1.ListSandboxPoliciesResponse + (*ReportPolicyStatusRequest)(nil), // 174: openshell.v1.ReportPolicyStatusRequest + (*ReportPolicyStatusResponse)(nil), // 175: openshell.v1.ReportPolicyStatusResponse + (*SandboxConfigurationAdmission)(nil), // 176: openshell.v1.SandboxConfigurationAdmission + (*ReportSandboxConfigurationRequest)(nil), // 177: openshell.v1.ReportSandboxConfigurationRequest + (*ReportSandboxConfigurationResponse)(nil), // 178: openshell.v1.ReportSandboxConfigurationResponse + (*SandboxPolicyRevision)(nil), // 179: openshell.v1.SandboxPolicyRevision + (*GetSandboxLogsRequest)(nil), // 180: openshell.v1.GetSandboxLogsRequest + (*PushSandboxLogsRequest)(nil), // 181: openshell.v1.PushSandboxLogsRequest + (*PushSandboxLogsResponse)(nil), // 182: openshell.v1.PushSandboxLogsResponse + (*GetSandboxLogsResponse)(nil), // 183: openshell.v1.GetSandboxLogsResponse + (*SupervisorMessage)(nil), // 184: openshell.v1.SupervisorMessage + (*GatewayMessage)(nil), // 185: openshell.v1.GatewayMessage + (*SupervisorHello)(nil), // 186: openshell.v1.SupervisorHello + (*SessionAccepted)(nil), // 187: openshell.v1.SessionAccepted + (*SessionRejected)(nil), // 188: openshell.v1.SessionRejected + (*SupervisorHeartbeat)(nil), // 189: openshell.v1.SupervisorHeartbeat + (*GatewayHeartbeat)(nil), // 190: openshell.v1.GatewayHeartbeat + (*ReportMainProcessExitRequest)(nil), // 191: openshell.v1.ReportMainProcessExitRequest + (*ReportMainProcessExitResponse)(nil), // 192: openshell.v1.ReportMainProcessExitResponse + (*FinalizeMainProcessExitRequest)(nil), // 193: openshell.v1.FinalizeMainProcessExitRequest + (*FinalizeMainProcessExitResponse)(nil), // 194: openshell.v1.FinalizeMainProcessExitResponse + (*RelayOpen)(nil), // 195: openshell.v1.RelayOpen + (*SshRelayTarget)(nil), // 196: openshell.v1.SshRelayTarget + (*TcpRelayTarget)(nil), // 197: openshell.v1.TcpRelayTarget + (*RelayInit)(nil), // 198: openshell.v1.RelayInit + (*RelayFrame)(nil), // 199: openshell.v1.RelayFrame + (*PeerRelayInit)(nil), // 200: openshell.v1.PeerRelayInit + (*PeerRelayFrame)(nil), // 201: openshell.v1.PeerRelayFrame + (*RelayOpenResult)(nil), // 202: openshell.v1.RelayOpenResult + (*RelayClose)(nil), // 203: openshell.v1.RelayClose + (*L7RequestSample)(nil), // 204: openshell.v1.L7RequestSample + (*DenialSummary)(nil), // 205: openshell.v1.DenialSummary + (*DenialGroupCount)(nil), // 206: openshell.v1.DenialGroupCount + (*NetworkActivitySummary)(nil), // 207: openshell.v1.NetworkActivitySummary + (*PolicyChunk)(nil), // 208: openshell.v1.PolicyChunk + (*DraftPolicyUpdate)(nil), // 209: openshell.v1.DraftPolicyUpdate + (*SubmitPolicyAnalysisRequest)(nil), // 210: openshell.v1.SubmitPolicyAnalysisRequest + (*SubmitPolicyAnalysisResponse)(nil), // 211: openshell.v1.SubmitPolicyAnalysisResponse + (*GetDraftPolicyRequest)(nil), // 212: openshell.v1.GetDraftPolicyRequest + (*GetDraftPolicyResponse)(nil), // 213: openshell.v1.GetDraftPolicyResponse + (*ApproveDraftChunkRequest)(nil), // 214: openshell.v1.ApproveDraftChunkRequest + (*ApproveDraftChunkResponse)(nil), // 215: openshell.v1.ApproveDraftChunkResponse + (*RejectDraftChunkRequest)(nil), // 216: openshell.v1.RejectDraftChunkRequest + (*RejectDraftChunkResponse)(nil), // 217: openshell.v1.RejectDraftChunkResponse + (*DraftChunkApproval)(nil), // 218: openshell.v1.DraftChunkApproval + (*ApproveAllDraftChunksRequest)(nil), // 219: openshell.v1.ApproveAllDraftChunksRequest + (*ApproveAllDraftChunksResponse)(nil), // 220: openshell.v1.ApproveAllDraftChunksResponse + (*EditDraftChunkRequest)(nil), // 221: openshell.v1.EditDraftChunkRequest + (*EditDraftChunkResponse)(nil), // 222: openshell.v1.EditDraftChunkResponse + (*UndoDraftChunkRequest)(nil), // 223: openshell.v1.UndoDraftChunkRequest + (*UndoDraftChunkResponse)(nil), // 224: openshell.v1.UndoDraftChunkResponse + (*ClearDraftChunksRequest)(nil), // 225: openshell.v1.ClearDraftChunksRequest + (*ClearDraftChunksResponse)(nil), // 226: openshell.v1.ClearDraftChunksResponse + (*GetDraftHistoryRequest)(nil), // 227: openshell.v1.GetDraftHistoryRequest + (*DraftHistoryEntry)(nil), // 228: openshell.v1.DraftHistoryEntry + (*GetDraftHistoryResponse)(nil), // 229: openshell.v1.GetDraftHistoryResponse + (*CreateWorkspaceRequest)(nil), // 230: openshell.v1.CreateWorkspaceRequest + (*CreateWorkspaceResponse)(nil), // 231: openshell.v1.CreateWorkspaceResponse + (*GetWorkspaceRequest)(nil), // 232: openshell.v1.GetWorkspaceRequest + (*GetWorkspaceResponse)(nil), // 233: openshell.v1.GetWorkspaceResponse + (*ListWorkspacesRequest)(nil), // 234: openshell.v1.ListWorkspacesRequest + (*ListWorkspacesResponse)(nil), // 235: openshell.v1.ListWorkspacesResponse + (*DeleteWorkspaceRequest)(nil), // 236: openshell.v1.DeleteWorkspaceRequest + (*DeleteWorkspaceResponse)(nil), // 237: openshell.v1.DeleteWorkspaceResponse + (*WorkspaceMember)(nil), // 238: openshell.v1.WorkspaceMember + (*AddWorkspaceMemberRequest)(nil), // 239: openshell.v1.AddWorkspaceMemberRequest + (*AddWorkspaceMemberResponse)(nil), // 240: openshell.v1.AddWorkspaceMemberResponse + (*RemoveWorkspaceMemberRequest)(nil), // 241: openshell.v1.RemoveWorkspaceMemberRequest + (*RemoveWorkspaceMemberResponse)(nil), // 242: openshell.v1.RemoveWorkspaceMemberResponse + (*ListWorkspaceMembersRequest)(nil), // 243: openshell.v1.ListWorkspaceMembersRequest + (*ListWorkspaceMembersResponse)(nil), // 244: openshell.v1.ListWorkspaceMembersResponse + (*ExtensionServiceCredential)(nil), // 245: openshell.v1.ExtensionServiceCredential + (*EndpointObservation)(nil), // 246: openshell.v1.EndpointObservation + (*ReportEndpointStatusRequest)(nil), // 247: openshell.v1.ReportEndpointStatusRequest + (*ReportEndpointStatusResponse)(nil), // 248: openshell.v1.ReportEndpointStatusResponse + (*EndpointStatus)(nil), // 249: openshell.v1.EndpointStatus + (*SandboxProvisioning)(nil), // 250: openshell.v1.SandboxProvisioning + (*SandboxServiceExposure)(nil), // 251: openshell.v1.SandboxServiceExposure + nil, // 252: openshell.v1.SandboxSpec.EnvironmentEntry + nil, // 253: openshell.v1.SandboxTemplate.LabelsEntry + nil, // 254: openshell.v1.SandboxTemplate.AnnotationsEntry + nil, // 255: openshell.v1.SandboxTemplate.EnvironmentEntry + nil, // 256: openshell.v1.SandboxWorkloadConfig.EnvironmentEntry + nil, // 257: openshell.v1.PlatformEvent.MetadataEntry + nil, // 258: openshell.v1.CreateSandboxRequest.LabelsEntry + nil, // 259: openshell.v1.CreateSandboxRequest.AnnotationsEntry + nil, // 260: openshell.v1.SandboxResponse.ServiceUrlsEntry + nil, // 261: openshell.v1.ExecSandboxRequest.EnvironmentEntry + nil, // 262: openshell.v1.SandboxLogLine.FieldsEntry + nil, // 263: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry + nil, // 264: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + nil, // 265: openshell.v1.ProviderProfile.AnnotationsEntry + nil, // 266: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + nil, // 267: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry + nil, // 268: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + nil, // 269: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + nil, // 270: openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry + nil, // 271: openshell.v1.UpdateConfigRequest.AnnotationsEntry + nil, // 272: openshell.v1.UpdateConfigResponse.AnnotationsEntry + nil, // 273: openshell.v1.SandboxPolicyRevision.ProvenanceEntry + nil, // 274: openshell.v1.CreateWorkspaceRequest.LabelsEntry + (*timestamppb.Timestamp)(nil), // 275: google.protobuf.Timestamp + (*datamodelv1.ObjectMeta)(nil), // 276: openshell.datamodel.v1.ObjectMeta + (*sandboxv1.SandboxPolicy)(nil), // 277: openshell.sandbox.v1.SandboxPolicy + (*structpb.Struct)(nil), // 278: google.protobuf.Struct + (*durationpb.Duration)(nil), // 279: google.protobuf.Duration + (*datamodelv1.WorkspaceSelector)(nil), // 280: openshell.datamodel.v1.WorkspaceSelector + (*datamodelv1.Provider)(nil), // 281: openshell.datamodel.v1.Provider + (sandboxv1.PolicySource)(0), // 282: openshell.sandbox.v1.PolicySource + (*sandboxv1.NetworkEndpoint)(nil), // 283: openshell.sandbox.v1.NetworkEndpoint + (*sandboxv1.NetworkBinary)(nil), // 284: openshell.sandbox.v1.NetworkBinary + (*sandboxv1.SettingValue)(nil), // 285: openshell.sandbox.v1.SettingValue + (*sandboxv1.NetworkPolicyRule)(nil), // 286: openshell.sandbox.v1.NetworkPolicyRule + (*sandboxv1.L7DenyRule)(nil), // 287: openshell.sandbox.v1.L7DenyRule + (*sandboxv1.L7Rule)(nil), // 288: openshell.sandbox.v1.L7Rule + (*datamodelv1.Workspace)(nil), // 289: openshell.datamodel.v1.Workspace + (*sandboxv1.GetSandboxConfigRequest)(nil), // 290: openshell.sandbox.v1.GetSandboxConfigRequest + (*sandboxv1.GetGatewayConfigRequest)(nil), // 291: openshell.sandbox.v1.GetGatewayConfigRequest + (*sandboxv1.GetSandboxConfigResponse)(nil), // 292: openshell.sandbox.v1.GetSandboxConfigResponse + (*sandboxv1.GetGatewayConfigResponse)(nil), // 293: openshell.sandbox.v1.GetGatewayConfigResponse } var file_openshell_proto_depIdxs = []int32{ - 274, // 0: openshell.v1.IssueSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp - 274, // 1: openshell.v1.RefreshSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp - 244, // 2: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential - 274, // 3: openshell.v1.RefreshSandboxTokenResponse.sandbox_expiration_time:type_name -> google.protobuf.Timestamp + 275, // 0: openshell.v1.IssueSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp + 275, // 1: openshell.v1.RefreshSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp + 245, // 2: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential + 275, // 3: openshell.v1.RefreshSandboxTokenResponse.sandbox_expiration_time:type_name -> google.protobuf.Timestamp 13, // 4: openshell.v1.HealthResponse.status:type_name -> openshell.v1.ServiceStatus 13, // 5: openshell.v1.GetGatewayInfoResponse.status:type_name -> openshell.v1.ServiceStatus - 30, // 6: openshell.v1.GetGatewayInfoResponse.compute_drivers:type_name -> openshell.v1.ComputeDriverInfo - 29, // 7: openshell.v1.GetGatewayInfoResponse.extensions:type_name -> openshell.v1.NegotiatedExtensionInfo + 31, // 6: openshell.v1.GetGatewayInfoResponse.compute_drivers:type_name -> openshell.v1.ComputeDriverInfo + 30, // 7: openshell.v1.GetGatewayInfoResponse.extensions:type_name -> openshell.v1.NegotiatedExtensionInfo 0, // 8: openshell.v1.NegotiatedExtensionInfo.kind:type_name -> openshell.v1.ExtensionKind - 31, // 9: openshell.v1.ComputeDriverInfo.capabilities:type_name -> openshell.v1.ComputeDriverCapabilities - 32, // 10: openshell.v1.ComputeDriverCapabilities.resource_capabilities:type_name -> openshell.v1.ResourceCapabilities - 33, // 11: openshell.v1.ResourceCapabilities.cpu:type_name -> openshell.v1.CpuResourceCapabilities - 34, // 12: openshell.v1.ResourceCapabilities.memory:type_name -> openshell.v1.MemoryResourceCapabilities - 35, // 13: openshell.v1.ResourceCapabilities.gpu:type_name -> openshell.v1.GpuResourceCapabilities - 275, // 14: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 37, // 15: openshell.v1.Sandbox.spec:type_name -> openshell.v1.SandboxSpec - 48, // 16: openshell.v1.Sandbox.status:type_name -> openshell.v1.SandboxStatus - 47, // 17: openshell.v1.Sandbox.created_from_workload_template:type_name -> openshell.v1.SandboxWorkloadTemplateProvenance - 251, // 18: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry - 40, // 19: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate - 276, // 20: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 38, // 21: openshell.v1.SandboxSpec.resource_requirements:type_name -> openshell.v1.ResourceRequirements + 32, // 9: openshell.v1.ComputeDriverInfo.capabilities:type_name -> openshell.v1.ComputeDriverCapabilities + 33, // 10: openshell.v1.ComputeDriverCapabilities.resource_capabilities:type_name -> openshell.v1.ResourceCapabilities + 34, // 11: openshell.v1.ResourceCapabilities.cpu:type_name -> openshell.v1.CpuResourceCapabilities + 35, // 12: openshell.v1.ResourceCapabilities.memory:type_name -> openshell.v1.MemoryResourceCapabilities + 36, // 13: openshell.v1.ResourceCapabilities.gpu:type_name -> openshell.v1.GpuResourceCapabilities + 276, // 14: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 38, // 15: openshell.v1.Sandbox.spec:type_name -> openshell.v1.SandboxSpec + 49, // 16: openshell.v1.Sandbox.status:type_name -> openshell.v1.SandboxStatus + 48, // 17: openshell.v1.Sandbox.created_from_workload_template:type_name -> openshell.v1.SandboxWorkloadTemplateProvenance + 252, // 18: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry + 41, // 19: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate + 277, // 20: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 39, // 21: openshell.v1.SandboxSpec.resource_requirements:type_name -> openshell.v1.ResourceRequirements 16, // 22: openshell.v1.SandboxSpec.restart_policy:type_name -> openshell.v1.SandboxRestartPolicy - 39, // 23: openshell.v1.ResourceRequirements.gpu:type_name -> openshell.v1.GpuResourceRequirements - 252, // 24: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry - 253, // 25: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry - 254, // 26: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry - 277, // 27: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct - 277, // 28: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct - 275, // 29: openshell.v1.SandboxWorkloadTemplate.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 42, // 30: openshell.v1.SandboxWorkloadTemplate.spec:type_name -> openshell.v1.SandboxWorkloadTemplateSpec - 43, // 31: openshell.v1.SandboxWorkloadTemplateSpec.workload:type_name -> openshell.v1.SandboxWorkloadConfig - 277, // 32: openshell.v1.SandboxWorkloadTemplateSpec.driver_config:type_name -> google.protobuf.Struct - 45, // 33: openshell.v1.SandboxWorkloadTemplateSpec.desired_service_level:type_name -> openshell.v1.SandboxServiceLevel - 255, // 34: openshell.v1.SandboxWorkloadConfig.environment:type_name -> openshell.v1.SandboxWorkloadConfig.EnvironmentEntry - 44, // 35: openshell.v1.SandboxWorkloadConfig.resources:type_name -> openshell.v1.SandboxResources - 39, // 36: openshell.v1.SandboxResources.gpu:type_name -> openshell.v1.GpuResourceRequirements - 46, // 37: openshell.v1.SandboxServiceLevel.startup:type_name -> openshell.v1.SandboxStartup - 278, // 38: openshell.v1.SandboxStartup.ready_within:type_name -> google.protobuf.Duration - 49, // 39: openshell.v1.SandboxStatus.conditions:type_name -> openshell.v1.SandboxCondition + 40, // 23: openshell.v1.ResourceRequirements.gpu:type_name -> openshell.v1.GpuResourceRequirements + 253, // 24: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry + 254, // 25: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry + 255, // 26: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry + 278, // 27: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct + 278, // 28: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct + 276, // 29: openshell.v1.SandboxWorkloadTemplate.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 43, // 30: openshell.v1.SandboxWorkloadTemplate.spec:type_name -> openshell.v1.SandboxWorkloadTemplateSpec + 44, // 31: openshell.v1.SandboxWorkloadTemplateSpec.workload:type_name -> openshell.v1.SandboxWorkloadConfig + 278, // 32: openshell.v1.SandboxWorkloadTemplateSpec.driver_config:type_name -> google.protobuf.Struct + 46, // 33: openshell.v1.SandboxWorkloadTemplateSpec.desired_service_level:type_name -> openshell.v1.SandboxServiceLevel + 256, // 34: openshell.v1.SandboxWorkloadConfig.environment:type_name -> openshell.v1.SandboxWorkloadConfig.EnvironmentEntry + 45, // 35: openshell.v1.SandboxWorkloadConfig.resources:type_name -> openshell.v1.SandboxResources + 40, // 36: openshell.v1.SandboxResources.gpu:type_name -> openshell.v1.GpuResourceRequirements + 47, // 37: openshell.v1.SandboxServiceLevel.startup:type_name -> openshell.v1.SandboxStartup + 279, // 38: openshell.v1.SandboxStartup.ready_within:type_name -> google.protobuf.Duration + 50, // 39: openshell.v1.SandboxStatus.conditions:type_name -> openshell.v1.SandboxCondition 1, // 40: openshell.v1.SandboxStatus.phase:type_name -> openshell.v1.SandboxPhase - 248, // 41: openshell.v1.SandboxStatus.endpoint_statuses:type_name -> openshell.v1.EndpointStatus - 175, // 42: openshell.v1.SandboxStatus.configuration_admission:type_name -> openshell.v1.SandboxConfigurationAdmission - 249, // 43: openshell.v1.SandboxStatus.provisioning:type_name -> openshell.v1.SandboxProvisioning - 274, // 44: openshell.v1.SandboxStatus.next_restart_time:type_name -> google.protobuf.Timestamp - 274, // 45: openshell.v1.SandboxStatus.main_process_started_time:type_name -> google.protobuf.Timestamp - 274, // 46: openshell.v1.SandboxCondition.transition_time:type_name -> google.protobuf.Timestamp - 274, // 47: openshell.v1.PlatformEvent.event_time:type_name -> google.protobuf.Timestamp - 256, // 48: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry - 279, // 49: openshell.v1.CreateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 37, // 50: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec - 257, // 51: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry - 258, // 52: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry - 250, // 53: openshell.v1.CreateSandboxRequest.service_exposures:type_name -> openshell.v1.SandboxServiceExposure - 279, // 54: openshell.v1.CreateSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 41, // 55: openshell.v1.CreateSandboxTemplateRequest.template:type_name -> openshell.v1.SandboxWorkloadTemplate - 279, // 56: openshell.v1.GetSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 279, // 57: openshell.v1.ListSandboxTemplatesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 279, // 58: openshell.v1.DeleteSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 41, // 59: openshell.v1.SandboxTemplateResponse.template:type_name -> openshell.v1.SandboxWorkloadTemplate - 41, // 60: openshell.v1.ListSandboxTemplatesResponse.templates:type_name -> openshell.v1.SandboxWorkloadTemplate + 249, // 41: openshell.v1.SandboxStatus.endpoint_statuses:type_name -> openshell.v1.EndpointStatus + 176, // 42: openshell.v1.SandboxStatus.configuration_admission:type_name -> openshell.v1.SandboxConfigurationAdmission + 250, // 43: openshell.v1.SandboxStatus.provisioning:type_name -> openshell.v1.SandboxProvisioning + 275, // 44: openshell.v1.SandboxStatus.next_restart_time:type_name -> google.protobuf.Timestamp + 275, // 45: openshell.v1.SandboxStatus.main_process_started_time:type_name -> google.protobuf.Timestamp + 275, // 46: openshell.v1.SandboxCondition.transition_time:type_name -> google.protobuf.Timestamp + 275, // 47: openshell.v1.PlatformEvent.event_time:type_name -> google.protobuf.Timestamp + 257, // 48: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry + 280, // 49: openshell.v1.CreateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 38, // 50: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec + 258, // 51: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry + 259, // 52: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry + 251, // 53: openshell.v1.CreateSandboxRequest.service_exposures:type_name -> openshell.v1.SandboxServiceExposure + 280, // 54: openshell.v1.CreateSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 42, // 55: openshell.v1.CreateSandboxTemplateRequest.template:type_name -> openshell.v1.SandboxWorkloadTemplate + 280, // 56: openshell.v1.GetSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 280, // 57: openshell.v1.ListSandboxTemplatesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 280, // 58: openshell.v1.DeleteSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 42, // 59: openshell.v1.SandboxTemplateResponse.template:type_name -> openshell.v1.SandboxWorkloadTemplate + 42, // 60: openshell.v1.ListSandboxTemplatesResponse.templates:type_name -> openshell.v1.SandboxWorkloadTemplate 17, // 61: openshell.v1.DeleteSandboxTemplateResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 279, // 62: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 63: openshell.v1.BeginRootfsTarStagingResponse.expiration_time:type_name -> google.protobuf.Timestamp - 279, // 64: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 279, // 65: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 279, // 66: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 279, // 67: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 279, // 68: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 279, // 69: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 279, // 70: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 279, // 71: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 36, // 72: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox - 259, // 73: openshell.v1.SandboxResponse.service_urls:type_name -> openshell.v1.SandboxResponse.ServiceUrlsEntry - 36, // 74: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox - 280, // 75: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 36, // 76: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox - 78, // 77: openshell.v1.AttachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt - 36, // 78: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox - 78, // 79: openshell.v1.DetachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt - 76, // 80: openshell.v1.ConfigSnapshotRevision.sandbox_config:type_name -> openshell.v1.SandboxConfigRevision - 74, // 81: openshell.v1.ConfigSnapshotRevision.provider_target:type_name -> openshell.v1.ProviderDesiredIdentity - 281, // 82: openshell.v1.SandboxConfigRevision.policy_source:type_name -> openshell.sandbox.v1.PolicySource + 280, // 62: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 275, // 63: openshell.v1.BeginRootfsTarStagingResponse.expiration_time:type_name -> google.protobuf.Timestamp + 280, // 64: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 280, // 65: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 280, // 66: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 280, // 67: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 280, // 68: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 280, // 69: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 280, // 70: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 280, // 71: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 37, // 72: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox + 260, // 73: openshell.v1.SandboxResponse.service_urls:type_name -> openshell.v1.SandboxResponse.ServiceUrlsEntry + 37, // 74: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox + 281, // 75: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 37, // 76: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox + 79, // 77: openshell.v1.AttachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt + 37, // 78: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox + 79, // 79: openshell.v1.DetachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt + 77, // 80: openshell.v1.ConfigSnapshotRevision.sandbox_config:type_name -> openshell.v1.SandboxConfigRevision + 75, // 81: openshell.v1.ConfigSnapshotRevision.provider_target:type_name -> openshell.v1.ProviderDesiredIdentity + 282, // 82: openshell.v1.SandboxConfigRevision.policy_source:type_name -> openshell.sandbox.v1.PolicySource 5, // 83: openshell.v1.ConfigUpdateOperation.component:type_name -> openshell.v1.ConfigComponent - 75, // 84: openshell.v1.ConfigUpdateOperation.target_revision:type_name -> openshell.v1.ConfigSnapshotRevision + 76, // 84: openshell.v1.ConfigUpdateOperation.target_revision:type_name -> openshell.v1.ConfigSnapshotRevision 7, // 85: openshell.v1.ConfigUpdateOperation.state:type_name -> openshell.v1.ConfigUpdateOperationState 6, // 86: openshell.v1.ConfigUpdateOperation.outcome:type_name -> openshell.v1.ConfigApplyOutcome - 274, // 87: openshell.v1.ConfigUpdateOperation.created_time:type_name -> google.protobuf.Timestamp - 274, // 88: openshell.v1.ConfigUpdateOperation.updated_time:type_name -> google.protobuf.Timestamp - 274, // 89: openshell.v1.ConfigUpdateOperation.completed_time:type_name -> google.protobuf.Timestamp + 275, // 87: openshell.v1.ConfigUpdateOperation.created_time:type_name -> google.protobuf.Timestamp + 275, // 88: openshell.v1.ConfigUpdateOperation.updated_time:type_name -> google.protobuf.Timestamp + 275, // 89: openshell.v1.ConfigUpdateOperation.completed_time:type_name -> google.protobuf.Timestamp 2, // 90: openshell.v1.ProviderMutationReceipt.kind:type_name -> openshell.v1.ProviderMutationKind - 74, // 91: openshell.v1.ProviderMutationReceipt.desired:type_name -> openshell.v1.ProviderDesiredIdentity - 274, // 92: openshell.v1.ProviderMutationReceipt.persisted_time:type_name -> google.protobuf.Timestamp + 75, // 91: openshell.v1.ProviderMutationReceipt.desired:type_name -> openshell.v1.ProviderDesiredIdentity + 275, // 92: openshell.v1.ProviderMutationReceipt.persisted_time:type_name -> google.protobuf.Timestamp 4, // 93: openshell.v1.ProviderReadinessObservation.reason:type_name -> openshell.v1.ProviderReadinessReason - 78, // 94: openshell.v1.ProviderReadinessStatus.receipt:type_name -> openshell.v1.ProviderMutationReceipt + 79, // 94: openshell.v1.ProviderReadinessStatus.receipt:type_name -> openshell.v1.ProviderMutationReceipt 3, // 95: openshell.v1.ProviderReadinessStatus.state:type_name -> openshell.v1.ProviderReadinessState 4, // 96: openshell.v1.ProviderReadinessStatus.reason:type_name -> openshell.v1.ProviderReadinessReason - 79, // 97: openshell.v1.ProviderReadinessStatus.observed:type_name -> openshell.v1.ProviderReadinessObservation - 274, // 98: openshell.v1.ProviderReadinessStatus.observed_time:type_name -> google.protobuf.Timestamp - 274, // 99: openshell.v1.ProviderReadinessStatus.evaluated_time:type_name -> google.protobuf.Timestamp - 77, // 100: openshell.v1.ProviderReadinessStatus.operation:type_name -> openshell.v1.ConfigUpdateOperation - 279, // 101: openshell.v1.GetSandboxProviderStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 80, // 102: openshell.v1.GetSandboxProviderStatusResponse.status:type_name -> openshell.v1.ProviderReadinessStatus - 79, // 103: openshell.v1.ReportProviderReadinessRequest.observation:type_name -> openshell.v1.ProviderReadinessObservation - 278, // 104: openshell.v1.ReportProviderReadinessResponse.report_interval:type_name -> google.protobuf.Duration - 278, // 105: openshell.v1.ReportProviderReadinessResponse.observation_ttl:type_name -> google.protobuf.Duration + 80, // 97: openshell.v1.ProviderReadinessStatus.observed:type_name -> openshell.v1.ProviderReadinessObservation + 275, // 98: openshell.v1.ProviderReadinessStatus.observed_time:type_name -> google.protobuf.Timestamp + 275, // 99: openshell.v1.ProviderReadinessStatus.evaluated_time:type_name -> google.protobuf.Timestamp + 78, // 100: openshell.v1.ProviderReadinessStatus.operation:type_name -> openshell.v1.ConfigUpdateOperation + 280, // 101: openshell.v1.GetSandboxProviderStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 81, // 102: openshell.v1.GetSandboxProviderStatusResponse.status:type_name -> openshell.v1.ProviderReadinessStatus + 80, // 103: openshell.v1.ReportProviderReadinessRequest.observation:type_name -> openshell.v1.ProviderReadinessObservation + 279, // 104: openshell.v1.ReportProviderReadinessResponse.report_interval:type_name -> google.protobuf.Duration + 279, // 105: openshell.v1.ReportProviderReadinessResponse.observation_ttl:type_name -> google.protobuf.Duration 17, // 106: openshell.v1.DeleteSandboxResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 279, // 107: openshell.v1.CreateSshSessionRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 108: openshell.v1.CreateSshSessionResponse.expiration_time:type_name -> google.protobuf.Timestamp - 279, // 109: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 279, // 110: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 279, // 111: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 95, // 112: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse - 279, // 113: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 17, // 114: openshell.v1.DeleteServiceResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 275, // 115: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 94, // 116: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint - 17, // 117: openshell.v1.RevokeSshSessionResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 279, // 118: openshell.v1.ExecSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 260, // 119: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry - 278, // 120: openshell.v1.ExecSandboxRequest.execution_timeout:type_name -> google.protobuf.Duration - 99, // 121: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout - 100, // 122: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr - 101, // 123: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit - 195, // 124: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget - 196, // 125: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget - 103, // 126: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit - 98, // 127: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest - 106, // 128: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize - 275, // 129: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 274, // 130: openshell.v1.SshSession.expiration_time:type_name -> google.protobuf.Timestamp - 279, // 131: openshell.v1.WatchSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 132: openshell.v1.WatchSandboxRequest.since_time:type_name -> google.protobuf.Timestamp - 36, // 133: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox - 110, // 134: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine - 50, // 135: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent - 111, // 136: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning - 208, // 137: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate - 274, // 138: openshell.v1.SandboxLogLine.event_time:type_name -> google.protobuf.Timestamp - 261, // 139: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry - 279, // 140: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 280, // 141: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 279, // 142: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 279, // 143: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 279, // 144: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 280, // 145: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 262, // 146: openshell.v1.UpdateProviderRequest.credential_expiration_times:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry - 279, // 147: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 280, // 148: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider - 78, // 149: openshell.v1.ProviderResponse.target_receipts:type_name -> openshell.v1.ProviderMutationReceipt - 280, // 150: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 279, // 151: openshell.v1.ListProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 279, // 152: openshell.v1.GetProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 140, // 153: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile - 278, // 154: openshell.v1.ProviderCredentialTokenGrant.cache_ttl:type_name -> google.protobuf.Duration - 123, // 155: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride - 8, // 156: openshell.v1.ProviderCredentialTokenGrant.grant_type:type_name -> openshell.v1.ProviderCredentialTokenGrantType - 124, // 157: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken - 129, // 158: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh - 125, // 159: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant - 9, // 160: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 278, // 161: openshell.v1.ProviderCredentialRefresh.refresh_before:type_name -> google.protobuf.Duration - 278, // 162: openshell.v1.ProviderCredentialRefresh.max_lifetime:type_name -> google.protobuf.Duration - 127, // 163: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial - 128, // 164: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput - 9, // 165: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 274, // 166: openshell.v1.ProviderCredentialRefreshStatus.expiration_time:type_name -> google.protobuf.Timestamp - 274, // 167: openshell.v1.ProviderCredentialRefreshStatus.next_refresh_time:type_name -> google.protobuf.Timestamp - 274, // 168: openshell.v1.ProviderCredentialRefreshStatus.last_refresh_time:type_name -> google.protobuf.Timestamp - 15, // 169: openshell.v1.ProviderCredentialRefreshStatus.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction - 274, // 170: openshell.v1.ProviderCredentialRefreshStatus.last_error_time:type_name -> google.protobuf.Timestamp - 279, // 171: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 130, // 172: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 279, // 173: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 9, // 174: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 263, // 175: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - 274, // 176: openshell.v1.ConfigureProviderRefreshRequest.expiration_time:type_name -> google.protobuf.Timestamp - 130, // 177: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 279, // 178: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 130, // 179: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 279, // 180: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 17, // 181: openshell.v1.DeleteProviderRefreshResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 10, // 182: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory - 126, // 183: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential - 282, // 184: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint - 283, // 185: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary - 131, // 186: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery - 264, // 187: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry - 141, // 188: openshell.v1.ProviderProfile.files:type_name -> openshell.v1.ProviderProfileFile - 140, // 189: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile - 140, // 190: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 279, // 191: openshell.v1.ImportProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 121, // 192: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 122, // 193: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 140, // 194: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 279, // 195: openshell.v1.UpdateProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 121, // 196: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem - 122, // 197: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 140, // 198: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile - 279, // 199: openshell.v1.LintProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 121, // 200: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 122, // 201: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 17, // 202: openshell.v1.DeleteProviderResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 279, // 203: openshell.v1.DeleteProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 17, // 204: openshell.v1.DeleteProviderProfileResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 154, // 205: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding - 265, // 206: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - 266, // 207: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expiration_times:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry - 267, // 208: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - 268, // 209: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - 4, // 210: openshell.v1.GetSandboxProviderEnvironmentResponse.readiness_reason:type_name -> openshell.v1.ProviderReadinessReason - 269, // 211: openshell.v1.GetSandboxProviderEnvironmentResponse.files:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry - 278, // 212: openshell.v1.ExchangeProviderSubjectTokenResponse.expires_after:type_name -> google.protobuf.Duration - 279, // 213: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 276, // 214: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 284, // 215: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue - 160, // 216: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation - 270, // 217: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry - 161, // 218: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule - 162, // 219: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint - 163, // 220: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule - 165, // 221: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules - 166, // 222: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules - 167, // 223: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary - 285, // 224: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 283, // 225: openshell.v1.L7RuleTarget.binaries:type_name -> openshell.sandbox.v1.NetworkBinary - 286, // 226: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule - 164, // 227: openshell.v1.AddDenyRules.target:type_name -> openshell.v1.L7RuleTarget - 287, // 228: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule - 164, // 229: openshell.v1.AddAllowRules.target:type_name -> openshell.v1.L7RuleTarget - 271, // 230: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry - 279, // 231: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 178, // 232: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision - 279, // 233: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 178, // 234: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision - 12, // 235: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus - 11, // 236: openshell.v1.SandboxConfigurationAdmission.state:type_name -> openshell.v1.ConfigurationAdmissionState - 175, // 237: openshell.v1.ReportSandboxConfigurationRequest.admission:type_name -> openshell.v1.SandboxConfigurationAdmission - 12, // 238: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus - 274, // 239: openshell.v1.SandboxPolicyRevision.created_time:type_name -> google.protobuf.Timestamp - 274, // 240: openshell.v1.SandboxPolicyRevision.loaded_time:type_name -> google.protobuf.Timestamp - 276, // 241: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 272, // 242: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry - 279, // 243: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 244: openshell.v1.GetSandboxLogsRequest.since_time:type_name -> google.protobuf.Timestamp - 110, // 245: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine - 110, // 246: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine - 185, // 247: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello - 188, // 248: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat - 201, // 249: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult - 202, // 250: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose - 186, // 251: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted - 187, // 252: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected - 189, // 253: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat - 194, // 254: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen - 202, // 255: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose - 278, // 256: openshell.v1.SessionAccepted.heartbeat_interval:type_name -> google.protobuf.Duration - 195, // 257: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget - 196, // 258: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget - 197, // 259: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit - 194, // 260: openshell.v1.PeerRelayInit.relay_open:type_name -> openshell.v1.RelayOpen - 199, // 261: openshell.v1.PeerRelayFrame.init:type_name -> openshell.v1.PeerRelayInit - 274, // 262: openshell.v1.DenialSummary.first_seen_time:type_name -> google.protobuf.Timestamp - 274, // 263: openshell.v1.DenialSummary.last_seen_time:type_name -> google.protobuf.Timestamp - 203, // 264: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample - 205, // 265: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount - 285, // 266: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 274, // 267: openshell.v1.PolicyChunk.created_time:type_name -> google.protobuf.Timestamp - 274, // 268: openshell.v1.PolicyChunk.decided_time:type_name -> google.protobuf.Timestamp - 274, // 269: openshell.v1.PolicyChunk.first_seen_time:type_name -> google.protobuf.Timestamp - 274, // 270: openshell.v1.PolicyChunk.last_seen_time:type_name -> google.protobuf.Timestamp - 276, // 271: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 276, // 272: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 279, // 273: openshell.v1.SubmitPolicyAnalysisRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 204, // 274: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary - 207, // 275: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk - 206, // 276: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary - 279, // 277: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 207, // 278: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk - 274, // 279: openshell.v1.GetDraftPolicyResponse.last_analyzed_time:type_name -> google.protobuf.Timestamp - 279, // 280: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 279, // 281: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 279, // 282: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 217, // 283: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval - 279, // 284: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 285, // 285: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 279, // 286: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 279, // 287: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 279, // 288: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 289: openshell.v1.DraftHistoryEntry.event_time:type_name -> google.protobuf.Timestamp - 227, // 290: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry - 273, // 291: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry - 288, // 292: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 288, // 293: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 288, // 294: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace - 17, // 295: openshell.v1.DeleteWorkspaceResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 275, // 296: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 14, // 297: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole - 279, // 298: openshell.v1.AddWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 14, // 299: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole - 237, // 300: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember - 279, // 301: openshell.v1.RemoveWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 17, // 302: openshell.v1.RemoveWorkspaceMemberResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 279, // 303: openshell.v1.ListWorkspaceMembersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 237, // 304: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember - 274, // 305: openshell.v1.ExtensionServiceCredential.expiration_time:type_name -> google.protobuf.Timestamp - 18, // 306: openshell.v1.EndpointObservation.result:type_name -> openshell.v1.EndpointResult - 245, // 307: openshell.v1.ReportEndpointStatusRequest.observations:type_name -> openshell.v1.EndpointObservation - 18, // 308: openshell.v1.EndpointStatus.last_result:type_name -> openshell.v1.EndpointResult - 274, // 309: openshell.v1.EndpointStatus.last_reported_time:type_name -> google.protobuf.Timestamp - 274, // 310: openshell.v1.SandboxProvisioning.configuration_change_time:type_name -> google.protobuf.Timestamp - 274, // 311: openshell.v1.SandboxProvisioning.first_rejection_time:type_name -> google.protobuf.Timestamp - 274, // 312: openshell.v1.SandboxProvisioning.deadline:type_name -> google.protobuf.Timestamp - 274, // 313: openshell.v1.SandboxProvisioning.timeout_time:type_name -> google.protobuf.Timestamp - 274, // 314: openshell.v1.SandboxProvisioning.cleanup_completed_time:type_name -> google.protobuf.Timestamp - 274, // 315: openshell.v1.SandboxProvisioning.cleanup_retry_time:type_name -> google.protobuf.Timestamp - 274, // 316: openshell.v1.SandboxProvisioning.attachment_change_time:type_name -> google.protobuf.Timestamp - 274, // 317: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp - 274, // 318: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp - 126, // 319: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential - 155, // 320: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding - 23, // 321: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest - 25, // 322: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest - 27, // 323: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest - 51, // 324: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest - 59, // 325: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest - 61, // 326: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest - 62, // 327: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest - 52, // 328: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest - 53, // 329: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest - 54, // 330: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest - 55, // 331: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest - 63, // 332: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest - 64, // 333: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest - 65, // 334: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest - 81, // 335: openshell.v1.OpenShell.GetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest - 66, // 336: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest - 67, // 337: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest - 68, // 338: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest - 86, // 339: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest - 88, // 340: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest - 89, // 341: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest - 90, // 342: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest - 92, // 343: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest - 96, // 344: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest - 98, // 345: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest - 104, // 346: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame - 105, // 347: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput - 112, // 348: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest - 113, // 349: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest - 114, // 350: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest - 119, // 351: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest - 120, // 352: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest - 144, // 353: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest - 146, // 354: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest - 148, // 355: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest - 115, // 356: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest - 132, // 357: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest - 134, // 358: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest - 136, // 359: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest - 138, // 360: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest - 116, // 361: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest - 151, // 362: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest - 289, // 363: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest - 290, // 364: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest - 159, // 365: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest - 169, // 366: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest - 171, // 367: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest - 173, // 368: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest - 246, // 369: openshell.v1.OpenShell.ReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest - 83, // 370: openshell.v1.OpenShell.ReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest - 176, // 371: openshell.v1.OpenShell.ReportSandboxConfiguration:input_type -> openshell.v1.ReportSandboxConfigurationRequest - 153, // 372: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest - 157, // 373: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest - 179, // 374: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest - 180, // 375: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest - 183, // 376: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage - 190, // 377: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest - 192, // 378: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest - 198, // 379: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame - 200, // 380: openshell.v1.OpenShell.PeerRelay:input_type -> openshell.v1.PeerRelayFrame - 83, // 381: openshell.v1.OpenShell.PeerReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest - 246, // 382: openshell.v1.OpenShell.PeerReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest - 81, // 383: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest - 108, // 384: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest - 209, // 385: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest - 211, // 386: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest - 213, // 387: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest - 215, // 388: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest - 218, // 389: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest - 220, // 390: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest - 222, // 391: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest - 224, // 392: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest - 226, // 393: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest - 19, // 394: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest - 21, // 395: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest - 229, // 396: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest - 231, // 397: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest - 233, // 398: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest - 235, // 399: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest - 238, // 400: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest - 240, // 401: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest - 242, // 402: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest - 24, // 403: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse - 26, // 404: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse - 28, // 405: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse - 69, // 406: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse - 60, // 407: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse - 69, // 408: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse - 70, // 409: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse - 56, // 410: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 56, // 411: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 57, // 412: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse - 58, // 413: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse - 71, // 414: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse - 72, // 415: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse - 73, // 416: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse - 82, // 417: openshell.v1.OpenShell.GetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse - 85, // 418: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse - 69, // 419: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse - 69, // 420: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse - 87, // 421: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse - 95, // 422: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse - 95, // 423: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse - 91, // 424: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse - 93, // 425: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse - 97, // 426: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse - 102, // 427: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent - 104, // 428: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame - 102, // 429: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent - 117, // 430: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse - 117, // 431: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse - 118, // 432: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse - 143, // 433: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse - 142, // 434: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse - 145, // 435: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse - 147, // 436: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse - 149, // 437: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse - 117, // 438: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse - 133, // 439: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse - 135, // 440: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse - 137, // 441: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse - 139, // 442: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse - 150, // 443: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse - 152, // 444: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse - 291, // 445: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse - 292, // 446: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse - 168, // 447: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse - 170, // 448: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse - 172, // 449: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse - 174, // 450: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse - 247, // 451: openshell.v1.OpenShell.ReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse - 84, // 452: openshell.v1.OpenShell.ReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse - 177, // 453: openshell.v1.OpenShell.ReportSandboxConfiguration:output_type -> openshell.v1.ReportSandboxConfigurationResponse - 156, // 454: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse - 158, // 455: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse - 182, // 456: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse - 181, // 457: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse - 184, // 458: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage - 191, // 459: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse - 193, // 460: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse - 198, // 461: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame - 200, // 462: openshell.v1.OpenShell.PeerRelay:output_type -> openshell.v1.PeerRelayFrame - 84, // 463: openshell.v1.OpenShell.PeerReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse - 247, // 464: openshell.v1.OpenShell.PeerReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse - 82, // 465: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse - 109, // 466: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent - 210, // 467: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse - 212, // 468: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse - 214, // 469: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse - 216, // 470: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse - 219, // 471: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse - 221, // 472: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse - 223, // 473: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse - 225, // 474: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse - 228, // 475: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse - 20, // 476: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse - 22, // 477: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse - 230, // 478: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse - 232, // 479: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse - 234, // 480: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse - 236, // 481: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse - 239, // 482: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse - 241, // 483: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse - 243, // 484: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse - 403, // [403:485] is the sub-list for method output_type - 321, // [321:403] is the sub-list for method input_type - 321, // [321:321] is the sub-list for extension type_name - 321, // [321:321] is the sub-list for extension extendee - 0, // [0:321] is the sub-list for field type_name + 280, // 107: openshell.v1.CreateSshSessionRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 275, // 108: openshell.v1.CreateSshSessionResponse.expiration_time:type_name -> google.protobuf.Timestamp + 280, // 109: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 19, // 110: openshell.v1.ExposeServiceRequest.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode + 280, // 111: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 280, // 112: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 96, // 113: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse + 280, // 114: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 17, // 115: openshell.v1.DeleteServiceResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 276, // 116: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 19, // 117: openshell.v1.ServiceEndpoint.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode + 95, // 118: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint + 17, // 119: openshell.v1.RevokeSshSessionResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 280, // 120: openshell.v1.ExecSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 261, // 121: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry + 279, // 122: openshell.v1.ExecSandboxRequest.execution_timeout:type_name -> google.protobuf.Duration + 100, // 123: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout + 101, // 124: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr + 102, // 125: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit + 196, // 126: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget + 197, // 127: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget + 104, // 128: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit + 99, // 129: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest + 107, // 130: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize + 276, // 131: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 275, // 132: openshell.v1.SshSession.expiration_time:type_name -> google.protobuf.Timestamp + 280, // 133: openshell.v1.WatchSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 275, // 134: openshell.v1.WatchSandboxRequest.since_time:type_name -> google.protobuf.Timestamp + 37, // 135: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox + 111, // 136: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine + 51, // 137: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent + 112, // 138: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning + 209, // 139: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate + 275, // 140: openshell.v1.SandboxLogLine.event_time:type_name -> google.protobuf.Timestamp + 262, // 141: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry + 280, // 142: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 281, // 143: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 280, // 144: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 280, // 145: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 280, // 146: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 281, // 147: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 263, // 148: openshell.v1.UpdateProviderRequest.credential_expiration_times:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry + 280, // 149: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 281, // 150: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider + 79, // 151: openshell.v1.ProviderResponse.target_receipts:type_name -> openshell.v1.ProviderMutationReceipt + 281, // 152: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 280, // 153: openshell.v1.ListProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 280, // 154: openshell.v1.GetProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 141, // 155: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile + 279, // 156: openshell.v1.ProviderCredentialTokenGrant.cache_ttl:type_name -> google.protobuf.Duration + 124, // 157: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride + 8, // 158: openshell.v1.ProviderCredentialTokenGrant.grant_type:type_name -> openshell.v1.ProviderCredentialTokenGrantType + 125, // 159: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken + 130, // 160: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh + 126, // 161: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant + 9, // 162: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 279, // 163: openshell.v1.ProviderCredentialRefresh.refresh_before:type_name -> google.protobuf.Duration + 279, // 164: openshell.v1.ProviderCredentialRefresh.max_lifetime:type_name -> google.protobuf.Duration + 128, // 165: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial + 129, // 166: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput + 9, // 167: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 275, // 168: openshell.v1.ProviderCredentialRefreshStatus.expiration_time:type_name -> google.protobuf.Timestamp + 275, // 169: openshell.v1.ProviderCredentialRefreshStatus.next_refresh_time:type_name -> google.protobuf.Timestamp + 275, // 170: openshell.v1.ProviderCredentialRefreshStatus.last_refresh_time:type_name -> google.protobuf.Timestamp + 15, // 171: openshell.v1.ProviderCredentialRefreshStatus.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction + 275, // 172: openshell.v1.ProviderCredentialRefreshStatus.last_error_time:type_name -> google.protobuf.Timestamp + 280, // 173: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 131, // 174: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 280, // 175: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 9, // 176: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 264, // 177: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + 275, // 178: openshell.v1.ConfigureProviderRefreshRequest.expiration_time:type_name -> google.protobuf.Timestamp + 131, // 179: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 280, // 180: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 131, // 181: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 280, // 182: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 17, // 183: openshell.v1.DeleteProviderRefreshResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 10, // 184: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory + 127, // 185: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential + 283, // 186: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint + 284, // 187: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 132, // 188: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery + 265, // 189: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry + 142, // 190: openshell.v1.ProviderProfile.files:type_name -> openshell.v1.ProviderProfileFile + 141, // 191: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile + 141, // 192: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 280, // 193: openshell.v1.ImportProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 122, // 194: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 123, // 195: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 141, // 196: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 280, // 197: openshell.v1.UpdateProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 122, // 198: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem + 123, // 199: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 141, // 200: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile + 280, // 201: openshell.v1.LintProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 122, // 202: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 123, // 203: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 17, // 204: openshell.v1.DeleteProviderResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 280, // 205: openshell.v1.DeleteProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 17, // 206: openshell.v1.DeleteProviderProfileResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 155, // 207: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding + 266, // 208: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + 267, // 209: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expiration_times:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry + 268, // 210: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + 269, // 211: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + 4, // 212: openshell.v1.GetSandboxProviderEnvironmentResponse.readiness_reason:type_name -> openshell.v1.ProviderReadinessReason + 270, // 213: openshell.v1.GetSandboxProviderEnvironmentResponse.files:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry + 279, // 214: openshell.v1.ExchangeProviderSubjectTokenResponse.expires_after:type_name -> google.protobuf.Duration + 280, // 215: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 277, // 216: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 285, // 217: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue + 161, // 218: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation + 271, // 219: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry + 162, // 220: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule + 163, // 221: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint + 164, // 222: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule + 166, // 223: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules + 167, // 224: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules + 168, // 225: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary + 286, // 226: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 284, // 227: openshell.v1.L7RuleTarget.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 287, // 228: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule + 165, // 229: openshell.v1.AddDenyRules.target:type_name -> openshell.v1.L7RuleTarget + 288, // 230: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule + 165, // 231: openshell.v1.AddAllowRules.target:type_name -> openshell.v1.L7RuleTarget + 272, // 232: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry + 280, // 233: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 179, // 234: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision + 280, // 235: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 179, // 236: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision + 12, // 237: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus + 11, // 238: openshell.v1.SandboxConfigurationAdmission.state:type_name -> openshell.v1.ConfigurationAdmissionState + 176, // 239: openshell.v1.ReportSandboxConfigurationRequest.admission:type_name -> openshell.v1.SandboxConfigurationAdmission + 12, // 240: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus + 275, // 241: openshell.v1.SandboxPolicyRevision.created_time:type_name -> google.protobuf.Timestamp + 275, // 242: openshell.v1.SandboxPolicyRevision.loaded_time:type_name -> google.protobuf.Timestamp + 277, // 243: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 273, // 244: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry + 280, // 245: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 275, // 246: openshell.v1.GetSandboxLogsRequest.since_time:type_name -> google.protobuf.Timestamp + 111, // 247: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine + 111, // 248: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine + 186, // 249: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello + 189, // 250: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat + 202, // 251: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult + 203, // 252: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose + 187, // 253: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted + 188, // 254: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected + 190, // 255: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat + 195, // 256: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen + 203, // 257: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose + 279, // 258: openshell.v1.SessionAccepted.heartbeat_interval:type_name -> google.protobuf.Duration + 196, // 259: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget + 197, // 260: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget + 198, // 261: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit + 195, // 262: openshell.v1.PeerRelayInit.relay_open:type_name -> openshell.v1.RelayOpen + 200, // 263: openshell.v1.PeerRelayFrame.init:type_name -> openshell.v1.PeerRelayInit + 275, // 264: openshell.v1.DenialSummary.first_seen_time:type_name -> google.protobuf.Timestamp + 275, // 265: openshell.v1.DenialSummary.last_seen_time:type_name -> google.protobuf.Timestamp + 204, // 266: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample + 206, // 267: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount + 286, // 268: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 275, // 269: openshell.v1.PolicyChunk.created_time:type_name -> google.protobuf.Timestamp + 275, // 270: openshell.v1.PolicyChunk.decided_time:type_name -> google.protobuf.Timestamp + 275, // 271: openshell.v1.PolicyChunk.first_seen_time:type_name -> google.protobuf.Timestamp + 275, // 272: openshell.v1.PolicyChunk.last_seen_time:type_name -> google.protobuf.Timestamp + 277, // 273: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 277, // 274: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 280, // 275: openshell.v1.SubmitPolicyAnalysisRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 205, // 276: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary + 208, // 277: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk + 207, // 278: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary + 280, // 279: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 208, // 280: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk + 275, // 281: openshell.v1.GetDraftPolicyResponse.last_analyzed_time:type_name -> google.protobuf.Timestamp + 280, // 282: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 280, // 283: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 280, // 284: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 218, // 285: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval + 280, // 286: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 286, // 287: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 280, // 288: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 280, // 289: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 280, // 290: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 275, // 291: openshell.v1.DraftHistoryEntry.event_time:type_name -> google.protobuf.Timestamp + 228, // 292: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry + 274, // 293: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry + 289, // 294: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 289, // 295: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 289, // 296: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace + 17, // 297: openshell.v1.DeleteWorkspaceResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 276, // 298: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 14, // 299: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole + 280, // 300: openshell.v1.AddWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 14, // 301: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole + 238, // 302: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember + 280, // 303: openshell.v1.RemoveWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 17, // 304: openshell.v1.RemoveWorkspaceMemberResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 280, // 305: openshell.v1.ListWorkspaceMembersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 238, // 306: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember + 275, // 307: openshell.v1.ExtensionServiceCredential.expiration_time:type_name -> google.protobuf.Timestamp + 18, // 308: openshell.v1.EndpointObservation.result:type_name -> openshell.v1.EndpointResult + 246, // 309: openshell.v1.ReportEndpointStatusRequest.observations:type_name -> openshell.v1.EndpointObservation + 18, // 310: openshell.v1.EndpointStatus.last_result:type_name -> openshell.v1.EndpointResult + 275, // 311: openshell.v1.EndpointStatus.last_reported_time:type_name -> google.protobuf.Timestamp + 275, // 312: openshell.v1.SandboxProvisioning.configuration_change_time:type_name -> google.protobuf.Timestamp + 275, // 313: openshell.v1.SandboxProvisioning.first_rejection_time:type_name -> google.protobuf.Timestamp + 275, // 314: openshell.v1.SandboxProvisioning.deadline:type_name -> google.protobuf.Timestamp + 275, // 315: openshell.v1.SandboxProvisioning.timeout_time:type_name -> google.protobuf.Timestamp + 275, // 316: openshell.v1.SandboxProvisioning.cleanup_completed_time:type_name -> google.protobuf.Timestamp + 275, // 317: openshell.v1.SandboxProvisioning.cleanup_retry_time:type_name -> google.protobuf.Timestamp + 275, // 318: openshell.v1.SandboxProvisioning.attachment_change_time:type_name -> google.protobuf.Timestamp + 19, // 319: openshell.v1.SandboxServiceExposure.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode + 275, // 320: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp + 275, // 321: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp + 127, // 322: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 156, // 323: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding + 24, // 324: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest + 26, // 325: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest + 28, // 326: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest + 52, // 327: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest + 60, // 328: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest + 62, // 329: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest + 63, // 330: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest + 53, // 331: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest + 54, // 332: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest + 55, // 333: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest + 56, // 334: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest + 64, // 335: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest + 65, // 336: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest + 66, // 337: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest + 82, // 338: openshell.v1.OpenShell.GetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest + 67, // 339: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest + 68, // 340: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest + 69, // 341: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest + 87, // 342: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest + 89, // 343: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest + 90, // 344: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest + 91, // 345: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest + 93, // 346: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest + 97, // 347: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest + 99, // 348: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest + 105, // 349: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame + 106, // 350: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput + 113, // 351: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest + 114, // 352: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest + 115, // 353: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest + 120, // 354: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest + 121, // 355: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest + 145, // 356: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest + 147, // 357: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest + 149, // 358: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest + 116, // 359: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest + 133, // 360: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest + 135, // 361: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest + 137, // 362: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest + 139, // 363: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest + 117, // 364: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest + 152, // 365: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest + 290, // 366: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest + 291, // 367: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest + 160, // 368: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest + 170, // 369: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest + 172, // 370: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest + 174, // 371: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest + 247, // 372: openshell.v1.OpenShell.ReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest + 84, // 373: openshell.v1.OpenShell.ReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest + 177, // 374: openshell.v1.OpenShell.ReportSandboxConfiguration:input_type -> openshell.v1.ReportSandboxConfigurationRequest + 154, // 375: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest + 158, // 376: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest + 180, // 377: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest + 181, // 378: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest + 184, // 379: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage + 191, // 380: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest + 193, // 381: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest + 199, // 382: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame + 201, // 383: openshell.v1.OpenShell.PeerRelay:input_type -> openshell.v1.PeerRelayFrame + 84, // 384: openshell.v1.OpenShell.PeerReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest + 247, // 385: openshell.v1.OpenShell.PeerReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest + 82, // 386: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest + 109, // 387: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest + 210, // 388: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest + 212, // 389: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest + 214, // 390: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest + 216, // 391: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest + 219, // 392: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest + 221, // 393: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest + 223, // 394: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest + 225, // 395: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest + 227, // 396: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest + 20, // 397: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest + 22, // 398: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest + 230, // 399: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest + 232, // 400: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest + 234, // 401: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest + 236, // 402: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest + 239, // 403: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest + 241, // 404: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest + 243, // 405: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest + 25, // 406: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse + 27, // 407: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse + 29, // 408: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse + 70, // 409: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse + 61, // 410: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse + 70, // 411: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse + 71, // 412: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse + 57, // 413: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 57, // 414: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 58, // 415: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse + 59, // 416: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse + 72, // 417: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse + 73, // 418: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse + 74, // 419: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse + 83, // 420: openshell.v1.OpenShell.GetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse + 86, // 421: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse + 70, // 422: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse + 70, // 423: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse + 88, // 424: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse + 96, // 425: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse + 96, // 426: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse + 92, // 427: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse + 94, // 428: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse + 98, // 429: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse + 103, // 430: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent + 105, // 431: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame + 103, // 432: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent + 118, // 433: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse + 118, // 434: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse + 119, // 435: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse + 144, // 436: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse + 143, // 437: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse + 146, // 438: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse + 148, // 439: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse + 150, // 440: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse + 118, // 441: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse + 134, // 442: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse + 136, // 443: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse + 138, // 444: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse + 140, // 445: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse + 151, // 446: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse + 153, // 447: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse + 292, // 448: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse + 293, // 449: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse + 169, // 450: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse + 171, // 451: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse + 173, // 452: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse + 175, // 453: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse + 248, // 454: openshell.v1.OpenShell.ReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse + 85, // 455: openshell.v1.OpenShell.ReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse + 178, // 456: openshell.v1.OpenShell.ReportSandboxConfiguration:output_type -> openshell.v1.ReportSandboxConfigurationResponse + 157, // 457: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse + 159, // 458: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse + 183, // 459: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse + 182, // 460: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse + 185, // 461: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage + 192, // 462: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse + 194, // 463: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse + 199, // 464: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame + 201, // 465: openshell.v1.OpenShell.PeerRelay:output_type -> openshell.v1.PeerRelayFrame + 85, // 466: openshell.v1.OpenShell.PeerReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse + 248, // 467: openshell.v1.OpenShell.PeerReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse + 83, // 468: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse + 110, // 469: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent + 211, // 470: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse + 213, // 471: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse + 215, // 472: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse + 217, // 473: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse + 220, // 474: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse + 222, // 475: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse + 224, // 476: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse + 226, // 477: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse + 229, // 478: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse + 21, // 479: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse + 23, // 480: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse + 231, // 481: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse + 233, // 482: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse + 235, // 483: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse + 237, // 484: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse + 240, // 485: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse + 242, // 486: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse + 244, // 487: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse + 406, // [406:488] is the sub-list for method output_type + 324, // [324:406] is the sub-list for method input_type + 324, // [324:324] is the sub-list for extension type_name + 324, // [324:324] is the sub-list for extension extendee + 0, // [0:324] is the sub-list for field type_name } func init() { file_openshell_proto_init() } @@ -20269,7 +20361,7 @@ func file_openshell_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_openshell_proto_rawDesc), len(file_openshell_proto_rawDesc)), - NumEnums: 19, + NumEnums: 20, NumMessages: 255, NumExtensions: 0, NumServices: 1, diff --git a/sdk/typescript/src/client.test.ts b/sdk/typescript/src/client.test.ts index 56dcdf3f2b..178a73cc6e 100644 --- a/sdk/typescript/src/client.test.ts +++ b/sdk/typescript/src/client.test.ts @@ -20,9 +20,16 @@ import { SandboxClient, SandboxTemplateClient, SCOPE_NAMES, + ServiceAuthorizationMode, STATUS_NAMES, } from './client.js'; -import { OpenShell, SandboxPhase, SandboxRestartPolicy, ServiceStatus } from './gen/openshell_pb.js'; +import { + OpenShell, + ServiceAuthorizationMode as ProtoServiceAuthorizationMode, + SandboxPhase, + SandboxRestartPolicy, + ServiceStatus, +} from './gen/openshell_pb.js'; import { PolicySource, SettingScope } from './gen/sandbox_pb.js'; import type { ExecInteractiveSession, ExecInteractiveSessionControl } from './index.js'; @@ -276,7 +283,9 @@ describe('exec / execStream', () => { describe('create', () => { it('sends create-time service exposures', async () => { - let created: { serviceExposures?: Array<{ service?: string; targetPort?: number }> } = {}; + let created: { + serviceExposures?: Array<{ service?: string; targetPort?: number; authorizationMode?: number }>; + } = {}; const sandbox = client({ createSandbox: (req) => { created = req; @@ -292,12 +301,29 @@ describe('create', () => { const result = await sandbox.create({ image: 'img', - serviceExposures: [{ targetPort: 4500 }, { service: 'metrics', targetPort: 9090 }], + serviceExposures: [ + { targetPort: 4500 }, + { + service: 'metrics', + targetPort: 9090, + authorizationMode: ServiceAuthorizationMode.BearerPassthrough, + }, + ], }); - expect(created.serviceExposures?.map(({ service, targetPort }) => ({ service, targetPort }))).toEqual([ - { service: '', targetPort: 4500 }, - { service: 'metrics', targetPort: 9090 }, + expect( + created.serviceExposures?.map(({ service, targetPort, authorizationMode }) => ({ + service, + targetPort, + authorizationMode, + })), + ).toEqual([ + { service: '', targetPort: 4500, authorizationMode: ProtoServiceAuthorizationMode.STRIP }, + { + service: 'metrics', + targetPort: 9090, + authorizationMode: ProtoServiceAuthorizationMode.BEARER_PASSTHROUGH, + }, ]); expect(result.serviceUrls).toEqual({ '': 'https://sb.example.test/', diff --git a/sdk/typescript/src/client.ts b/sdk/typescript/src/client.ts index 2f26a71f91..e73bccd314 100644 --- a/sdk/typescript/src/client.ts +++ b/sdk/typescript/src/client.ts @@ -22,6 +22,7 @@ import type { Sandbox, SandboxWorkloadTemplate, UpdateConfigResponse } from './g import { type ExecSandboxInputSchema, OpenShell, + ServiceAuthorizationMode as ProtoServiceAuthorizationMode, SandboxPhase, SandboxRestartPolicy, type SandboxSpecSchema, @@ -168,6 +169,23 @@ export interface ServiceExposure { service?: string; /** Loopback TCP port inside the sandbox. */ targetPort: number; + /** Handling for an incoming application Authorization header. */ + authorizationMode?: ServiceAuthorizationMode; +} + +export enum ServiceAuthorizationMode { + Strip = 'strip', + BearerPassthrough = 'bearer_passthrough', +} + +function serviceAuthorizationModeToProto(mode: ServiceAuthorizationMode | undefined): ProtoServiceAuthorizationMode { + switch (mode) { + case ServiceAuthorizationMode.BearerPassthrough: + return ProtoServiceAuthorizationMode.BEARER_PASSTHROUGH; + case ServiceAuthorizationMode.Strip: + case undefined: + return ProtoServiceAuthorizationMode.STRIP; + } } export interface SandboxFromTemplateSpec { @@ -1023,6 +1041,7 @@ export class SandboxClient { spec.serviceExposures?.map((exposure) => ({ service: exposure.service ?? '', targetPort: exposure.targetPort, + authorizationMode: serviceAuthorizationModeToProto(exposure.authorizationMode), })) ?? [], }); return sandboxRef(resp.sandbox, resp.serviceUrls); @@ -1049,6 +1068,7 @@ export class SandboxClient { spec.serviceExposures?.map((exposure) => ({ service: exposure.service ?? '', targetPort: exposure.targetPort, + authorizationMode: serviceAuthorizationModeToProto(exposure.authorizationMode), })) ?? [], }); return sandboxRef(resp.sandbox, resp.serviceUrls); diff --git a/sdk/typescript/src/index.ts b/sdk/typescript/src/index.ts index 974136fc37..9229f8bcbe 100644 --- a/sdk/typescript/src/index.ts +++ b/sdk/typescript/src/index.ts @@ -53,7 +53,14 @@ export type { WaitOptions, WorkspaceListScope, } from './client.js'; -export { errorCode, OpenShellClient, Pager, SandboxClient, SandboxTemplateClient } from './client.js'; +export { + errorCode, + OpenShellClient, + Pager, + SandboxClient, + SandboxTemplateClient, + ServiceAuthorizationMode, +} from './client.js'; export type { ErrorInfo, FieldViolation, SdkErrorCode } from './errors.js'; export { fromConnect, SdkError } from './errors.js'; export type { ClientCredentialsOptions, OidcTokenProvider } from './oidc.js'; diff --git a/skills/openshell-cli/SKILL.md b/skills/openshell-cli/SKILL.md index 572eadc96d..3977018a01 100644 --- a/skills/openshell-cli/SKILL.md +++ b/skills/openshell-cli/SKILL.md @@ -886,11 +886,13 @@ openshell sandbox create \ --name my-app \ --from my-app:latest \ --expose 8080 \ + --expose-authorization-mode bearer-passthrough \ --detach \ -- ./start-server.sh # Expose and manage an HTTP service through the gateway. -openshell service expose my-app 8080 web +openshell service expose my-app 8080 web \ + --authorization-mode bearer-passthrough openshell service list my-app openshell service list my-app --output json openshell service get my-app web @@ -905,6 +907,19 @@ request and keeps the sandbox running. Add `--output json` for automation; the result contains a `service_urls` map whose empty key is the unnamed endpoint. Use `openshell service expose` after creation to add or update named endpoints. +Exposed services strip `Authorization` by default. Select +`bearer-passthrough` only when the application inside the sandbox authenticates +its own clients. This mode accepts either no `Authorization` header or exactly +one non-empty Bearer credential and forwards that value unchanged. It rejects +duplicate, malformed, or non-Bearer authorization before contacting the +application. The application remains responsible for validating the token, and +the raw token reaches the sandbox process, so never log it. Service routes +bypass control-plane RPC authorization, but they still use the gateway's +existing listener, domain routing, and TLS configuration, including any client +certificate requirement. See the published +[sandbox service documentation](https://docs.nvidia.com/openshell/latest/how-it-works/sandboxes/overview.md) +for the complete security contract. + Prefer loopback binds unless the user explicitly needs LAN-visible local access. --- diff --git a/tests/suites/features/Cargo.lock b/tests/suites/features/Cargo.lock index 268f8bac92..8561dc97df 100644 --- a/tests/suites/features/Cargo.lock +++ b/tests/suites/features/Cargo.lock @@ -38,7 +38,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" dependencies = [ "aws-lc-sys", - "untrusted", + "untrusted 0.7.1", "zeroize", ] @@ -278,7 +278,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -804,7 +804,7 @@ checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -903,12 +903,15 @@ dependencies = [ "noyalib", "prost", "rand", + "rustls", + "rustls-pemfile", "serde", "serde_json", "sha1", "sha2", "tempfile", "tokio", + "tokio-rustls", "tokio-stream", "tonic", "tonic-prost", @@ -1122,6 +1125,20 @@ dependencies = [ "bitflags", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-hash" version = "2.1.3" @@ -1138,7 +1155,52 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted 0.9.0", ] [[package]] @@ -1303,7 +1365,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1312,6 +1374,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.119" @@ -1361,7 +1429,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1438,7 +1506,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1452,6 +1520,16 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "tokio-rustls" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.19" @@ -1603,6 +1681,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -1724,6 +1808,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -1733,6 +1826,70 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "wit-bindgen" version = "0.57.1" From cccc610e577195d5236611db31cd2b191c340ae0 Mon Sep 17 00:00:00 2001 From: KorneAlex Date: Tue, 29 Sep 2026 17:29:31 +0100 Subject: [PATCH 07/33] CARRY: fix(konflux): include CPU architecture in UBI RPM repo IDs Conforma rejects repository IDs such as ubi-9-baseos-rpms because allowed IDs include the architecture. Qualify the multi-arch inputs with $basearch and record the expanded ID in the lockfiles without changing package versions. CodeReady Builder uses codeready-builder-for-ubi-9-$basearch-rpms. The section names in the UBI image's ubi.repo omit the architecture. Signed-off-by: KorneAlex --- deploy/konflux/cli/rpms.in.yaml | 9 +++++++-- deploy/konflux/gateway/rpms.in.yaml | 9 +++++++-- deploy/konflux/sandbox/rpms.in.yaml | 11 ++++++++--- deploy/konflux/supervisor/rpms.in.yaml | 9 +++++++-- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/deploy/konflux/cli/rpms.in.yaml b/deploy/konflux/cli/rpms.in.yaml index 60d526144e..0ec7e8d2b3 100644 --- a/deploy/konflux/cli/rpms.in.yaml +++ b/deploy/konflux/cli/rpms.in.yaml @@ -18,11 +18,16 @@ # UBI content is public, so repos are inlined here rather than read from the # base image; rpm-lockfile-prototype resolves versions from the CDN and cannot # read /etc/yum.repos.d from inside the base image. +# Conforma allows only arch-qualified repo IDs (known_rpm_repositories.yml). +# Do not copy section names from the UBI image's ubi.repo: ubi-9-baseos-rpms +# omits the architecture and fails rpm_repos.ids_known. $basearch is expanded +# per package URL in the lockfile. CodeReady Builder is +# codeready-builder-for-ubi-9-$basearch-rpms. contentOrigin: repos: - - repoid: ubi-9-baseos-rpms + - repoid: ubi-9-for-$basearch-baseos-rpms baseurl: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/$basearch/baseos/os/ - - repoid: ubi-9-appstream-rpms + - repoid: ubi-9-for-$basearch-appstream-rpms baseurl: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/$basearch/appstream/os/ packages: # Build stage: build tools diff --git a/deploy/konflux/gateway/rpms.in.yaml b/deploy/konflux/gateway/rpms.in.yaml index f30f1dcf4f..a4fa980fea 100644 --- a/deploy/konflux/gateway/rpms.in.yaml +++ b/deploy/konflux/gateway/rpms.in.yaml @@ -15,11 +15,16 @@ # UBI content is public, so repos are inlined here rather than copied from the # image; rpm-lockfile-prototype resolves versions from the CDN and cannot read # /etc/yum.repos.d from inside the base image. +# Conforma allows only arch-qualified repo IDs (known_rpm_repositories.yml). +# Do not copy section names from the UBI image's ubi.repo: ubi-9-baseos-rpms +# omits the architecture and fails rpm_repos.ids_known. $basearch is expanded +# per package URL in the lockfile. CodeReady Builder is +# codeready-builder-for-ubi-9-$basearch-rpms. contentOrigin: repos: - - repoid: ubi-9-baseos-rpms + - repoid: ubi-9-for-$basearch-baseos-rpms baseurl: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/$basearch/baseos/os/ - - repoid: ubi-9-appstream-rpms + - repoid: ubi-9-for-$basearch-appstream-rpms baseurl: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/$basearch/appstream/os/ packages: # Build stage: Rust toolchain is prefetched from static.rust-lang.org (see diff --git a/deploy/konflux/sandbox/rpms.in.yaml b/deploy/konflux/sandbox/rpms.in.yaml index 5d36c1fab9..1af12c879c 100644 --- a/deploy/konflux/sandbox/rpms.in.yaml +++ b/deploy/konflux/sandbox/rpms.in.yaml @@ -17,15 +17,20 @@ # UBI content is public, so repos are inlined here rather than copied from the # image; rpm-lockfile-prototype resolves versions from the CDN and cannot read # /etc/yum.repos.d from inside the base image. +# Conforma allows only arch-qualified repo IDs (known_rpm_repositories.yml). +# Do not copy section names from the UBI image's ubi.repo: ubi-9-baseos-rpms +# omits the architecture and fails rpm_repos.ids_known. $basearch is expanded +# per package URL in the lockfile. CodeReady Builder is +# codeready-builder-for-ubi-9-$basearch-rpms. contentOrigin: repos: - - repoid: ubi-9-baseos-rpms + - repoid: ubi-9-for-$basearch-baseos-rpms baseurl: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/$basearch/baseos/os/ - - repoid: ubi-9-appstream-rpms + - repoid: ubi-9-for-$basearch-appstream-rpms baseurl: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/$basearch/appstream/os/ # glibc-static (for `-C target-feature=+crt-static`) ships in CodeReady # Builder, whose content is public on the UBI CDN. - - repoid: ubi-9-codeready-builder-rpms + - repoid: codeready-builder-for-ubi-9-$basearch-rpms baseurl: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/$basearch/codeready-builder/os/ packages: # Build stage: static glibc + build tools diff --git a/deploy/konflux/supervisor/rpms.in.yaml b/deploy/konflux/supervisor/rpms.in.yaml index 9bfdcdd0c6..bbbb3fb81a 100644 --- a/deploy/konflux/supervisor/rpms.in.yaml +++ b/deploy/konflux/supervisor/rpms.in.yaml @@ -15,11 +15,16 @@ # UBI content is public, so repos are inlined here rather than copied from the # image; rpm-lockfile-prototype resolves versions from the CDN and cannot read # /etc/yum.repos.d from inside the base image. +# Conforma allows only arch-qualified repo IDs (known_rpm_repositories.yml). +# Do not copy section names from the UBI image's ubi.repo: ubi-9-baseos-rpms +# omits the architecture and fails rpm_repos.ids_known. $basearch is expanded +# per package URL in the lockfile. CodeReady Builder is +# codeready-builder-for-ubi-9-$basearch-rpms. contentOrigin: repos: - - repoid: ubi-9-baseos-rpms + - repoid: ubi-9-for-$basearch-baseos-rpms baseurl: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/$basearch/baseos/os/ - - repoid: ubi-9-appstream-rpms + - repoid: ubi-9-for-$basearch-appstream-rpms baseurl: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/$basearch/appstream/os/ packages: # Build stage: build tools From e73cf143d520fd75a74c72b76df511db73a11639 Mon Sep 17 00:00:00 2001 From: KorneAlex Date: Wed, 30 Sep 2026 22:35:29 +0100 Subject: [PATCH 08/33] CARRY: chore(konflux): regenerate UBI RPM lockfiles Regenerate the lockfiles with rpm-lockfile-prototype after rebasing onto main, so MintMaker sees tool output instead of a hand-merged lockfile. Signed-off-by: KorneAlex --- deploy/konflux/cli/rpms.lock.yaml | 634 +++++++++--------- deploy/konflux/gateway/rpms.lock.yaml | 820 +++++++++++------------ deploy/konflux/sandbox/rpms.lock.yaml | 568 ++++++++-------- deploy/konflux/supervisor/rpms.lock.yaml | 584 ++++++++-------- 4 files changed, 1303 insertions(+), 1303 deletions(-) diff --git a/deploy/konflux/cli/rpms.lock.yaml b/deploy/konflux/cli/rpms.lock.yaml index 2e8542ba36..d7b4fc100f 100644 --- a/deploy/konflux/cli/rpms.lock.yaml +++ b/deploy/konflux/cli/rpms.lock.yaml @@ -5,1043 +5,1043 @@ arches: - arch: aarch64 packages: - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cmake-3.31.8-3.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 11655593 checksum: sha256:2a77fe1c3784083dcdebdd548c16d823b3e4a5adb8d6c00e36841aa633eab53b name: cmake evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cmake-data-3.31.8-3.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 2829291 checksum: sha256:1cdc2e88a996c575b750483c8f562674e4b50a6ab414c7bbe6f6b641c1db7bd9 name: cmake-data evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cmake-filesystem-3.31.8-3.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 19282 checksum: sha256:69a498723354740357fc3f4b4cd235da38fadd98835da193bec0c0850f68f3ad name: cmake-filesystem evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cpp-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 10798392 checksum: sha256:eb16ef369b981c0dca6149e971a63f74ea09c09f1c638086e3cda1e0591ac21b name: cpp evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/e/emacs-filesystem-27.2-18.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 9495 checksum: sha256:49d7b88a05a72c15b78191a987e6def04fda8e2e4ff75711f715d0c0ecadc60f name: emacs-filesystem evr: 1:27.2-18.el9 sourcerpm: emacs-27.2-18.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gawk-all-langpacks-5.1.0-6.el9_8.1.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 218086 checksum: sha256:d38712f7aa4e5b3821ba899d6b4f62821407f6f239e17bf240ca488fb1d35376 name: gawk-all-langpacks evr: 5.1.0-6.el9_8.1 sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 31291354 checksum: sha256:542e635ac17b548cc03ab4347438d49f96837c1d91d12714b6b2764ec566156c name: gcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-c++-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 12994215 checksum: sha256:aabe892798a2272d65c269fd6aa14d3b3dfc782c98f92f8fda30c2a4fa33bf6d name: gcc-c++ evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/git-core-2.52.0-1.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 5392428 checksum: sha256:7082917982981bf611c089e7d9d53b63e969af61a37cfd65b5c4081d5b260a1c name: git-core evr: 2.52.0-1.el9 sourcerpm: git-2.52.0-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/glibc-devel-2.34-275.el9_8.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 576947 checksum: sha256:aef79b53955c2ec67efdc39cb91148b377f8719a8b68cf76c7ddcffbcb6b8bf0 name: glibc-devel evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 2925601 checksum: sha256:7e82c856a00206976aede9e7b3865d0a2363b1262c0f9e1949ba29c7b9c47281 name: kernel-headers evr: 5.14.0-687.53.1.el9_8 sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libasan-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 409047 checksum: sha256:854a84e72d40c2a062d112b93f8a694044fd7dc5b3afe7a869a448a12844c3e6 name: libasan evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 67120 checksum: sha256:3763354a5f45d886f9976eec20eb34f8afc2144c69ffba07de546f2820893c70 name: libmpc evr: 1.2.1-4.el9 sourcerpm: libmpc-1.2.1-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libstdc++-devel-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 2520018 checksum: sha256:5a2474c2e817b008212e5a94770d8bfbaad17e9ebba2a38d734907e594ac2aac name: libstdc++-devel evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libtool-ltdl-2.4.6-46.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 37581 checksum: sha256:85f38e641398438f7f08526d7003f47e47a14369798464fd67c465134258e964 name: libtool-ltdl evr: 2.4.6-46.el9 sourcerpm: libtool-2.4.6-46.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libubsan-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 178996 checksum: sha256:d0adfda8f1d8ddf05a46292228e31cf887d731e7999154603e148e3d70d1f182 name: libubsan evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libuv-1.42.0-2.el9_4.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 150129 checksum: sha256:4dc8a40da74e0f9823356460ee11f183c70f382953700fffef0c448198a677cc name: libuv evr: 1:1.42.0-2.el9_4 sourcerpm: libuv-1.42.0-2.el9_4.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libxcrypt-devel-4.4.18-3.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 33051 checksum: sha256:9d621f33df35b9c274b8d65457d6c67fc1522b6c62cf7b2341a4a99f39a93507 name: libxcrypt-devel evr: 4.4.18-3.el9 sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/o/openssl-devel-3.5.8-1.el9_8.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 5039228 checksum: sha256:49aec716fb44d8a07528f59a635d40a47a847b05e6cf57735994e9c2430efa12 name: openssl-devel evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/p/python-unversioned-command-3.9.25-7.el9_8.3.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 15791 checksum: sha256:6942913da91f52227ca9b22c6bd4269f49682a807beb0380dbdafe8c0a51d86e name: python-unversioned-command evr: 3.9.25-7.el9_8.3 sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/a/acl-2.4.0-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 84537 checksum: sha256:241864fdb68d73b5a63df6269ae0895aec7c08e723fb0506fe446c148c9dad3f name: acl evr: 2.4.0-1.el9_8 sourcerpm: acl-2.4.0-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/a/alternatives-1.24-2.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 42137 checksum: sha256:6f7c0667ac015bc0d40836c9f55c73ebf65a209069f69aa8f58e6b4655c820a8 name: alternatives evr: 1.24-2.el9 sourcerpm: chkconfig-1.24-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/a/audit-libs-3.1.5-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 128901 checksum: sha256:11880ec70b575841843cbee0853e03e50a0506321ea0a6f76c0c8145d79ae531 name: audit-libs evr: 3.1.5-8.el9 sourcerpm: audit-3.1.5-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/basesystem-11-13.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 8229 checksum: sha256:f498b0813fa1a825d550e8e3a9e42255eabfa18e6fc96adfc6cc8fa7e16dd513 name: basesystem evr: 11-13.el9 sourcerpm: basesystem-11-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/bash-5.1.8-9.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1760045 checksum: sha256:7dc1febec9c2fb184ed4407f8a188ab267b7e46b3534866f702c6266008ababa name: bash evr: 5.1.8-9.el9 sourcerpm: bash-5.1.8-9.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/binutils-2.35.2-72.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 5021411 checksum: sha256:72e9fd9c4976413060df02e37d358fca208ab144d78e321f448a488d50967155 name: binutils evr: 2.35.2-72.el9 sourcerpm: binutils-2.35.2-72.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/binutils-gold-2.35.2-72.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 908723 checksum: sha256:269bb24ded2f23dcdb74c04597b13281ce6ac47a87a14831ee639d985323bd04 name: binutils-gold evr: 2.35.2-72.el9 sourcerpm: binutils-2.35.2-72.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/bzip2-libs-1.0.8-11.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 47655 checksum: sha256:8267a866b9289ac4e4a92cb4642adcdeff97c2ed816ddda87ed5d5e9d9431a2f name: bzip2-libs evr: 1.0.8-11.el9 sourcerpm: bzip2-1.0.8-11.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/ca-certificates-2025.2.80_v9.0.305-91.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1072208 checksum: sha256:0554bf65d573950e7d550a07bf7d0b3def85ca3b45a7fbde4cce7cadd9a476a7 name: ca-certificates evr: 2025.2.80_v9.0.305-91.el9 sourcerpm: ca-certificates-2025.2.80_v9.0.305-91.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/coreutils-8.32-41.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1175876 checksum: sha256:22827aedd764c1ab706086965859e7f8fef0d719ac0b3d8735f6b8e53b0a13e9 name: coreutils evr: 8.32-41.el9_8.1 sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 2115646 checksum: sha256:b449955249a6d2da7369522a5ac2759919d36633e129ba88924057814906d5f5 name: coreutils-common evr: 8.32-41.el9_8.1 sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/cracklib-2.9.6-28.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 102026 checksum: sha256:d85216b672a15e5dd8cc771b853e3b73e832034949ee23998d8403609fba00a2 name: cracklib evr: 2.9.6-28.el9 sourcerpm: cracklib-2.9.6-28.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/cracklib-dicts-2.9.6-28.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 3829400 checksum: sha256:807345f95c448cb58d4db8d059f76e4c139ef029887d59b431efd20db934745a name: cracklib-dicts evr: 2.9.6-28.el9 sourcerpm: cracklib-2.9.6-28.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/crypto-policies-20260224-1.gitea0f072.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 98707 checksum: sha256:a9ad86b8df75a7c9c29c6f6dd4b8f78cfa1f42b492eff0d11b82c457d37a7f9f name: crypto-policies evr: 20260224-1.gitea0f072.el9_8 sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/crypto-policies-scripts-20260224-1.gitea0f072.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 111065 checksum: sha256:cec44db94e9132008a7fe4e1aa764a29e9c125989c9ac2411f3cf6f8fb669dfd name: crypto-policies-scripts evr: 20260224-1.gitea0f072.el9_8 sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/cyrus-sasl-lib-2.1.27-22.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 771760 checksum: sha256:7e4f331fc477f0a8482c825ab1b6bfec7f4007481f4eb53fde7fa0ef2d1f6cde name: cyrus-sasl-lib evr: 2.1.27-22.el9 sourcerpm: cyrus-sasl-2.1.27-22.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/d/dbus-1.12.20-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 8025 checksum: sha256:5178f638660d1699fb06caeeac91f41c07da59b500e94adf2653df892f2cbdf8 name: dbus evr: 1:1.12.20-8.el9 sourcerpm: dbus-1.12.20-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/d/dbus-broker-28-9.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 174219 checksum: sha256:ffebc8de0cd9ed86122973be161b617aa46ce1e020f61f9e8e5a42246d4bd495 name: dbus-broker evr: 28-9.el9_8 sourcerpm: dbus-broker-28-9.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/d/dbus-common-1.12.20-8.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 18551 checksum: sha256:298f1cada3cbcef6713098b9925694a0e30e8566f7a5bdbd72384520cf6c8360 name: dbus-common evr: 1:1.12.20-8.el9 sourcerpm: dbus-1.12.20-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-debuginfod-client-0.194-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 42824 checksum: sha256:bbcf05d0b46d42c85807d774788edc39528a6898bd880baf11fb77729f59272d name: elfutils-debuginfod-client evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-default-yama-scope-0.194-1.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 8949 checksum: sha256:c473f42c0fec97e2830580110509e4522bc62d9b11761d062499354a0bf58959 name: elfutils-default-yama-scope evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-libelf-0.194-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 203006 checksum: sha256:04ff63b7b669b16827f3688baa0be4a2782f1405db3c6d3ee03c0e4cebc2cdf2 name: elfutils-libelf evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-libs-0.194-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 271645 checksum: sha256:b90a6ad3e465995538785a2536986ad705625daf606d6258bf23d1dd29aec208 name: elfutils-libs evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.5.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 122957 checksum: sha256:d13bd77f429835b303d388e24811fb935060be4788c8fe4cf87703640f2337e0 name: expat evr: 2.5.0-6.el9_8.5 sourcerpm: expat-2.5.0-6.el9_8.5.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/f/filesystem-3.16-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 5003914 checksum: sha256:484bc41109c49066cf350344150abe144e63263e0fafa0bf12c5a47f853e6a49 name: filesystem evr: 3.16-5.el9 sourcerpm: filesystem-3.16-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/f/findutils-4.8.0-7.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 564807 checksum: sha256:158af4d5ecbd8b87f0da762ea1655bd4c86512071a95d8307eda3e0b3991105d name: findutils evr: 1:4.8.0-7.el9 sourcerpm: findutils-4.8.0-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gawk-5.1.0-6.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1026105 checksum: sha256:54365d2a6150079c2dc5003eeb94ada32fc15801b5db05422361f02ed58e6772 name: gawk evr: 5.1.0-6.el9_8.1 sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 60311 checksum: sha256:74fffe15dd7f5a41c7d1990c2804defa1b45fb845da29465b73a81d5866e8a72 name: gdbm-libs evr: 1:1.23-1.el9 sourcerpm: gdbm-1.23-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-2.34-275.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1813928 checksum: sha256:e40a78100f731b5f5a1b235880a0aaad5b08bb32e6521e90535b7f4a94c6e9e9 name: glibc evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-common-2.34-275.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 312665 checksum: sha256:24e1c7189d101531c526dd3cc1a42ab62d89f874824b54fb6b518ec24f190554 name: glibc-common evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-gconv-extra-2.34-275.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1829737 checksum: sha256:7f2f23c07b84b3cd3bacb905dffaca4f4474298e936f3b10b93390d4127591da name: glibc-gconv-extra evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-minimal-langpack-2.34-275.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 30881 checksum: sha256:b1348c4c4fe3da979f342b0c27ff0d33a11688274a0b6708292d7750bbc8d853 name: glibc-minimal-langpack evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gmp-6.2.0-13.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 275679 checksum: sha256:df01d909e4613514b1844d6ca26d0bcdff8a659762e507188d04ed046fb0cec4 name: gmp evr: 1:6.2.0-13.el9 sourcerpm: gmp-6.2.0-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/grep-3.6-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 276244 checksum: sha256:583a247a199901d44dc8a96d46010e15f6211f98f7c61ba089825155b0562520 name: grep evr: 3.6-5.el9 sourcerpm: grep-3.6-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gzip-1.12-2.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 171305 checksum: sha256:efafc848fdaa3a8e5e77baddc07abc7d800dc973efd44ecf492bc64dca4fabe6 name: gzip evr: 1.12-2.el9_8 sourcerpm: gzip-1.12-2.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/j/json-c-0.14-11.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 45052 checksum: sha256:fff625bf4f0753eb7323b8933d264f3cc5c992bfecba8b082b204849297149cc name: json-c evr: 0.14-11.el9 sourcerpm: json-c-0.14-11.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/k/keyutils-libs-1.6.3-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 34341 checksum: sha256:d747ed6e1916d8ea400c89ad6078a8c298e30d652ec21985c93539e34c587a73 name: keyutils-libs evr: 1.6.3-1.el9 sourcerpm: keyutils-1.6.3-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/k/kmod-libs-28-11.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 62168 checksum: sha256:58526b701eb3a72de98062c58b56524c35916a7b1191bb1a846da33be5a5f709 name: kmod-libs evr: 28-11.el9 sourcerpm: kmod-28-11.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/k/krb5-libs-1.21.1-10.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 793489 checksum: sha256:f8bbc9abe0da1ebe5bc028154bd54d465fe0682ddbb64c45882b84ff09f40e1d name: krb5-libs evr: 1.21.1-10.el9_8 sourcerpm: krb5-1.21.1-10.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/less-590-6.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 165028 checksum: sha256:fa762484ba40e0b7eb1c25531a66a0b578b6141cabb6f73d865c13ccdf75c1c9 name: less evr: 590-6.el9 sourcerpm: less-590-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libacl-2.4.0-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 31468 checksum: sha256:70ba010505e9805254f772c3dd9cd9e6176fc9e007e8c9be7204c44f85d8bbd2 name: libacl evr: 2.4.0-1.el9_8 sourcerpm: acl-2.4.0-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libarchive-3.5.3-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 400797 checksum: sha256:7fd7a2981416def9d96892ea11aec3b9af146a95cae11af59a2fdee22fb52516 name: libarchive evr: 3.5.3-11.el9_8 sourcerpm: libarchive-3.5.3-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libatomic-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 26108 checksum: sha256:bebe2e8fd98c64d1667e3de1eb3751b7b641bf5d0cd870ed6445fea5c4526326 name: libatomic evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libattr-2.6.0-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 23276 checksum: sha256:ec08036348dbe2ee41645bdb96eb485b9db5121ec0524258b0639426a22a49bc name: libattr evr: 2.6.0-1.el9_8 sourcerpm: attr-2.6.0-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libblkid-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 113931 checksum: sha256:2c38ac06d3a267b62fc5ea4e149a25c4287c19157f4f18e0f7edea4787b27e15 name: libblkid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libbrotli-1.0.9-9.el9_7.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 325179 checksum: sha256:f5237abc90191238333c1214da97b5202c8a15c2be3ab401ee10d95343cfdf17 name: libbrotli evr: 1.0.9-9.el9_7 sourcerpm: brotli-1.0.9-9.el9_7.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcap-2.48-10.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 78021 checksum: sha256:1ac3014c33b84d7a492b99d46d47940b096e034a3d5886e16ace7159724be012 name: libcap evr: 2.48-10.el9_8.1 sourcerpm: libcap-2.48-10.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcap-ng-0.8.2-7.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 36033 checksum: sha256:dc4eae31749196c0043225c6749e7306ff71f081c09cbdb2fc98a561087c4474 name: libcap-ng evr: 0.8.2-7.el9 sourcerpm: libcap-ng-0.8.2-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcbor-0.7.0-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 59368 checksum: sha256:93a2f44044ab11225b1123bc9df4f4d09c0a5f3251818e7d144ca64fd12c0957 name: libcbor evr: 0.7.0-5.el9 sourcerpm: libcbor-0.7.0-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcom_err-1.46.5-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 26607 checksum: sha256:b7e5c8fe9d9f15864966f46c124659de6cb9137d01e213b3e8cac00d10aab55a name: libcom_err evr: 1.46.5-8.el9 sourcerpm: e2fsprogs-1.46.5-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.7.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 292483 checksum: sha256:408a3da221a1d3856a4b9ab7a7c70901430f71521ef9d05f9b847c0deb27b160 name: libcurl evr: 7.76.1-40.el9_8.7 sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libdb-5.3.28-57.el9_6.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 727417 checksum: sha256:3a912b2a0a6226695a5773138ce5ce090c9fb155151dffe732b8d52e6dd22d63 name: libdb evr: 5.3.28-57.el9_6 sourcerpm: libdb-5.3.28-57.el9_6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libeconf-0.4.1-7.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 32324 checksum: sha256:8a5e117c2690c82835c6013f1fbac37b026f3e953fbffbd84c2f5ef6cf8df571 name: libeconf evr: 0.4.1-7.el9_8 sourcerpm: libeconf-0.4.1-7.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libedit-3.1-39.20210216cvs.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 110092 checksum: sha256:93964f8c06574404f4a5b44781b698f556fbc22f7ae3c82d4d540619772f6816 name: libedit evr: 3.1-39.20210216cvs.el9 sourcerpm: libedit-3.1-39.20210216cvs.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libevent-2.1.13-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 268476 checksum: sha256:27a85302d64c15bfd383929eedcdeabef9bfc8f0a1e085b0501d0de73cb2bf74 name: libevent evr: 2.1.13-1.el9_8 sourcerpm: libevent-2.1.13-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libfdisk-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 156711 checksum: sha256:af043918fc50ce5b3de50c48c3de0143140b692fbf639db6f259270c4211a776 name: libfdisk evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libffi-3.4.2-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 38554 checksum: sha256:d33e180b97a603542cb6f1a78b1c3b0ce4af1bc59ee0bb32620c98a629726bc4 name: libffi evr: 3.4.2-8.el9 sourcerpm: libffi-3.4.2-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libfido2-1.13.0-2.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 100573 checksum: sha256:e56e963635b92f407471c7c5698d602135b135bda4515ecc75ac52dd1d38c7e4 name: libfido2 evr: 1.13.0-2.el9 sourcerpm: libfido2-1.13.0-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgcc-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 81211 checksum: sha256:6923218fdef581189a4b51c7ba158083597f1c6df08cae021a1d90e9d61938a9 name: libgcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgcrypt-1.10.0-13.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 469908 checksum: sha256:4a270fae0cf2f5ad846ce530664bbde72e798bb4a8196afb8fd2db93086c31c9 name: libgcrypt evr: 1.10.0-13.el9_8 sourcerpm: libgcrypt-1.10.0-13.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgomp-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 262138 checksum: sha256:c8b3788fee442304e4158c802ff413df27d394b82f19c0f34ff43b5d3760470c name: libgomp evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgpg-error-1.42-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 222476 checksum: sha256:aee968114aed0238eb26cff42ec9b0819ad32e2bac99aa8124d55b480806aca5 name: libgpg-error evr: 1.42-5.el9 sourcerpm: libgpg-error-1.42-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libidn2-2.3.0-7.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 107549 checksum: sha256:657925cd0fc0abc03cc83ff3688e131a452ea673a5dcb815cd0fc168bf962fc7 name: libidn2 evr: 2.3.0-7.el9 sourcerpm: libidn2-2.3.0-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libmount-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 140081 checksum: sha256:152aee3abc8d97a37ff4ce2f5027d7e16e93ff2c77d25e900258da54e6fcc64e name: libmount evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libnghttp2-1.43.0-6.el9_8.2.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 79650 checksum: sha256:eefb6d331e38314bce55c28e02d67d022c83e41b4d5de91f86d9d846b381ac48 name: libnghttp2 evr: 1.43.0-6.el9_8.2 sourcerpm: nghttp2-1.43.0-6.el9_8.2.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libpkgconf-1.7.3-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 38310 checksum: sha256:9bdfccf6b092e0683aa6984f7c6caa737b30c0b1495e16abb03b5d1a5f8e787a name: libpkgconf evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libpsl-0.21.1-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 67300 checksum: sha256:08968334789ba764986d3beb4745de28eb1e2ed401a03dba9d80e75e3179aa76 name: libpsl evr: 0.21.1-5.el9 sourcerpm: libpsl-0.21.1-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libpwquality-1.4.4-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 125712 checksum: sha256:1657d94bbd79f93dc7a79d474316813bde681ce3a7f62f73314ec4d630e39349 name: libpwquality evr: 1.4.4-8.el9 sourcerpm: libpwquality-1.4.4-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libseccomp-2.5.2-2.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 76024 checksum: sha256:fc1d5e93483d166ca7f2acb50c04c181db2f3e7b89dba8edc6b1e5f2b0f10619 name: libseccomp evr: 2.5.2-2.el9 sourcerpm: libseccomp-2.5.2-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libselinux-3.6-3.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 89531 checksum: sha256:3d7249adbf19206e319cd24acc2e01b0da39975aa3e5af73bdb6c6d438108fac name: libselinux evr: 3.6-3.el9 sourcerpm: libselinux-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsemanage-3.6-5.el9_6.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 120963 checksum: sha256:233d8270827b9166ad11827599800d2a09284d29e73af09c7a12bae251a9463c name: libsemanage evr: 3.6-5.el9_6 sourcerpm: libsemanage-3.6-5.el9_6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsepol-3.6-3.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 326966 checksum: sha256:496ed9e2d7fac9704afe764eab4c2c43b4a47e8c229c14498dd19786f98f80c0 name: libsepol evr: 3.6-3.el9 sourcerpm: libsepol-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsigsegv-2.13-4.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 30566 checksum: sha256:0998ac158161c9d5f3b97c5dc6e35becd84da0ddc5d347a8af581ada529b3b5c name: libsigsegv evr: 2.13-4.el9 sourcerpm: libsigsegv-2.13-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsmartcols-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 67440 checksum: sha256:1704e73a566c920796d877c7bc3e5a96ea0c0c4194109c7b085c1128c01856af name: libsmartcols evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libssh-0.10.4-19.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 223114 checksum: sha256:80f3962d3eb780ca6d6d4f8c6841b003a907d758ad8ad1c3d785e9cf327672f6 name: libssh evr: 0.10.4-19.el9_8 sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libssh-config-0.10.4-19.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 14764 checksum: sha256:ce06b99793b9e5abc50a58ebc3ef11037f2efbdb5f819a25f8de4994613194d4 name: libssh-config evr: 0.10.4-19.el9_8 sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libstdc++-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 723913 checksum: sha256:ae00c5009a2ee4682ec732cde66d3f4fcfc1a7099ba7b3701767d1748a4f2683 name: libstdc++ evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libtasn1-4.16.0-10.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 80446 checksum: sha256:322524934c9b1f0714d2299705dbd41ec93451078e8144babc88c51bd19f6e07 name: libtasn1 evr: 4.16.0-10.el9_8 sourcerpm: libtasn1-4.16.0-10.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libunistring-0.9.10-15.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 503151 checksum: sha256:f68934935fc209e7c595c5619df75f822cc832803e3ea6de2c92e3b91b4d5008 name: libunistring evr: 0.9.10-15.el9 sourcerpm: libunistring-0.9.10-15.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libutempter-1.2.1-6.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 30505 checksum: sha256:d352371cbb7d5bd0c53fc699df953c8c1f184b056690b3c4571e57a6634015c5 name: libutempter evr: 1.2.1-6.el9 sourcerpm: libutempter-1.2.1-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libuuid-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 32555 checksum: sha256:4d7bb4144053a30067a82423fb6e88fd08c7a69bd256eb21b08c0e3f4dbbaee6 name: libuuid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libverto-0.3.2-3.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 24651 checksum: sha256:80d6e32c111ab9c0b2c607475b6a6691cdf6abaec19fde27043e8710a94a8f0c name: libverto evr: 0.3.2-3.el9 sourcerpm: libverto-0.3.2-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libxcrypt-4.4.18-3.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 127655 checksum: sha256:f05030123425a5033bcca3f260313cafc199bc7bca57e9fb13c335bd087c35a7 name: libxcrypt evr: 4.4.18-3.el9 sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libxml2-2.9.13-14.el9_8.5.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 754850 checksum: sha256:39e0ffab5e42aa3688a39a9f27cecb77ee8dbf03682ca5b38c3e15ebc5493863 name: libxml2 evr: 2.9.13-14.el9_8.5 sourcerpm: libxml2-2.9.13-14.el9_8.5.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libzstd-1.5.5-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 283159 checksum: sha256:1229ed44dc7a68278682d7697c41d0abd7daedd242d90c6dc58a9aa6e76f9e6f name: libzstd evr: 1.5.5-1.el9 sourcerpm: zstd-1.5.5-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/lz4-libs-1.9.3-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 70696 checksum: sha256:e1dbd2c38a65b135427c7c8fe988ea70dc95f7e26c4c8177b7dcb23925020015 name: lz4-libs evr: 1.9.3-5.el9 sourcerpm: lz4-1.9.3-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/m/make-4.3-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 550249 checksum: sha256:351a22b0e6744bd329b1b0f22d9c3b69a6da970b575e6c76190cc84b0fe77450 name: make evr: 1:4.3-8.el9 sourcerpm: make-4.3-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/m/mpfr-4.1.0-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 249973 checksum: sha256:c238f7451d1fcc5431bef2904ba56a0bea51d28337ccb692f6d6a09286043a65 name: mpfr evr: 4.1.0-10.el9 sourcerpm: mpfr-4.1.0-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/n/ncurses-base-6.2-12.20210508.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 97840 checksum: sha256:d62dfd41f9688efa2cf1ceedb96084c63e297fbdcfd1e72bc6757c730092b60c name: ncurses-base evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/n/ncurses-libs-6.2-12.20210508.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 324624 checksum: sha256:b5dd452392d2f97bb050c9f5e5376998652c567dcbd8f035d26659b1b551b5c9 name: ncurses-libs evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openldap-2.6.8-4.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 291500 checksum: sha256:fd684316480b2f9a9448d550c2509e37016710ca0724ff3d17d91fa0be2bdc4e name: openldap evr: 2.6.8-4.el9 sourcerpm: openldap-2.6.8-4.el9.src.rpm - - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssh-9.9p1-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms - size: 432812 - checksum: sha256:d8bcf6348f5ee9d840e7f74eaaa53801cb9c48c1184c7254dd06aa73f597c690 + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssh-9.9p1-12.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 432970 + checksum: sha256:e2cc40546db9b067d9d969187aa1fea68ce399ee5f53e44ed91df6f2fc23f49c name: openssh - evr: 9.9p1-11.el9_8 - sourcerpm: openssh-9.9p1-11.el9_8.src.rpm - - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssh-clients-9.9p1-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms - size: 770368 - checksum: sha256:b5f49e9e5d66075859596aa71f62bc8cc951d50129dd43543b6aacd22386b1c1 + evr: 9.9p1-12.el9_8 + sourcerpm: openssh-9.9p1-12.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssh-clients-9.9p1-12.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 770215 + checksum: sha256:8601b26e577d6f8f0726061d96d941a4d8c2d5f0700a2bea5a957bbc17da6c22 name: openssh-clients - evr: 9.9p1-11.el9_8 - sourcerpm: openssh-9.9p1-11.el9_8.src.rpm + evr: 9.9p1-12.el9_8 + sourcerpm: openssh-9.9p1-12.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-3.5.8-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1546056 checksum: sha256:1ef4001b9a9df4695c80e986d3c8ceb7900bb6925f86fde09d16aff9f8d733f5 name: openssl evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 14220 checksum: sha256:158193d2f965db318148ec76e9347b530ac1f5379d849012c0a04d3c50cda478 name: openssl-fips-provider evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-fips-provider-so-3.0.7-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 529340 checksum: sha256:22374a51f8a529dcfcf3b3ebbb2095103e0e811a28953535e5a62e26d1233301 name: openssl-fips-provider-so evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-libs-3.5.8-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 2294194 checksum: sha256:23134af6ea097b94d8261367db01f91604ef3b508654caf4f7399e5c142abcc8 name: openssl-libs evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/p11-kit-0.26.4-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 589112 checksum: sha256:b9391ea6618098782c9325ccda3c9ca8bc0f3b035bd4f113a793cea18026c75e name: p11-kit evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/p11-kit-trust-0.26.4-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 162392 checksum: sha256:a57761123cd5836faf3d40251d16124557ffb452443bfa14cf59ac16e6c99970 name: p11-kit-trust evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pam-1.5.1-28.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 642233 checksum: sha256:12271815e3a5d35dbc4a8e1e3b52442b0d00b833816ef3a923da427a3bca561a name: pam evr: 1.5.1-28.el9_8.1 sourcerpm: pam-1.5.1-28.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pcre-8.44-4.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 187289 checksum: sha256:099feef7e71b82cf0234e37d824fc81353d51dee55694e05181fa686ab50efae name: pcre evr: 8.44-4.el9 sourcerpm: pcre-8.44-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pcre2-10.40-6.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 224938 checksum: sha256:29285f81cef68f73b4f8ff81ee8fdf4ceaa007933302119ed1615e4aa1091613 name: pcre2 evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pcre2-syntax-10.40-6.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 147926 checksum: sha256:d386b5e9b3a4b077b2ba143882e605750855dd3354f13c55fa12ed26908cb442 name: pcre2-syntax evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pkgconf-1.7.3-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 45196 checksum: sha256:aa38a3951a690d721a815ea8f9b01995a85f35a8540d8075205821011d0385e6 name: pkgconf evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pkgconf-m4-1.7.3-10.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 16054 checksum: sha256:91bafd6e06099451f60288327b275cfcc651822f6145176a157c6b0fa5131e02 name: pkgconf-m4 evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pkgconf-pkg-config-1.7.3-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 12398 checksum: sha256:47f1f744f96a2f3d360bc129837738dcebb1ee5032effc4472a891eea1d6a907 name: pkgconf-pkg-config evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/publicsuffix-list-dafsa-20210518-3.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 60882 checksum: sha256:e6ec3390a736b085f403168c512a6b2b6f8e12a8fd5a4459f1c7dbbff2b67c33 name: publicsuffix-list-dafsa evr: 20210518-3.el9 sourcerpm: publicsuffix-list-20210518-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-3.9.25-7.el9_8.3.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 33143 checksum: sha256:67e6d2eca7f6558030dd215a4d2d3ede810231faad0f317eb2bcbc7e9ac619ce name: python3 evr: 3.9.25-7.el9_8.3 sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-libs-3.9.25-7.el9_8.3.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 8470487 checksum: sha256:014bbdef3d3d00d09c34df9aaf377337e338e31c4106da6c98b844339fcd50d6 name: python3-libs evr: 3.9.25-7.el9_8.3 sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-pip-wheel-21.3.1-2.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1198443 checksum: sha256:918041963ad5c3b91276bf1ed3307280673ca8352e2e632bbb3847b33d2bc15a name: python3-pip-wheel evr: 21.3.1-2.el9_8 sourcerpm: python-pip-21.3.1-2.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-setuptools-wheel-53.0.0-15.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 479203 checksum: sha256:36dacb345e21bc0308ef2508f0c93995520a15ef0b56aab3593186c8dc9c0c5a name: python3-setuptools-wheel evr: 53.0.0-15.el9 sourcerpm: python-setuptools-53.0.0-15.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/r/readline-8.1-4.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 219015 checksum: sha256:2ae424b368c6747124b51b205b9e11d74aeaff56b3de90e8cbd36012e0d17707 name: readline evr: 8.1-4.el9 sourcerpm: readline-8.1-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/r/redhat-release-9.8-1.0.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 61683 checksum: sha256:fa7f1d93927c7f8c6f6563a8d221af659074f026e4b12cd74d456b0db1878164 name: redhat-release evr: 9.8-1.0.el9 sourcerpm: redhat-release-9.8-1.0.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/sed-4.8-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 315893 checksum: sha256:b73d314a8ef322a690bb69c49cb0dbd9a5ff18d2ba6b2973e18d2c076a52b62a name: sed evr: 4.8-10.el9 sourcerpm: sed-4.8-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/setup-2.13.7-10.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 153791 checksum: sha256:0891d395ce067121c28932534237ad1ce231f2bfa987411ad62e73a12d11eb6a name: setup evr: 2.13.7-10.el9 sourcerpm: setup-2.13.7-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/shadow-utils-4.9-16.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1244527 checksum: sha256:ccc46a8ea5f30d071e075ad53b5d39d191cfca4614090435528f2d2f944f88a2 name: shadow-utils evr: 2:4.9-16.el9 sourcerpm: shadow-utils-4.9-16.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/sqlite-libs-3.34.1-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 660770 checksum: sha256:0fbb8043f9c02870c831da433f69b856a6674d02b60306d9cc01149ec89ed239 name: sqlite-libs evr: 3.34.1-11.el9_8 sourcerpm: sqlite-3.34.1-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/systemd-252-67.el9_8.6.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 4135917 checksum: sha256:758ae3df6d76cace670ce7af7caa557a7cf8807a2160ccb30c49f7d8ec018749 name: systemd evr: 252-67.el9_8.6 sourcerpm: systemd-252-67.el9_8.6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/systemd-libs-252-67.el9_8.6.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 629233 checksum: sha256:55e58c413ee69f19a69ba25905f96343076e21abe54cdff51f0d80e48be06769 name: systemd-libs evr: 252-67.el9_8.6 sourcerpm: systemd-252-67.el9_8.6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/systemd-pam-252-67.el9_8.6.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 247880 checksum: sha256:736a6bbb73b0ef682ddfe356bb49578c2a8f17c6685ca762fcc1636003a4f5f3 name: systemd-pam evr: 252-67.el9_8.6 sourcerpm: systemd-252-67.el9_8.6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/systemd-rpm-macros-252-67.el9_8.6.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 40844 checksum: sha256:974a0352427799559a3e90dcdb38200d767631452cca7717c5b0fbe7760df313 name: systemd-rpm-macros evr: 252-67.el9_8.6 sourcerpm: systemd-252-67.el9_8.6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/t/tar-1.34-13.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 909271 checksum: sha256:ee4fa57c4bb87613f6fbd4b785a9e6162d43f046d1bbdd5022771652b931e9d0 name: tar evr: 2:1.34-13.el9_8 sourcerpm: tar-1.34-13.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 933286 checksum: sha256:8d6196e02853c0900c3c2e2367665beffb62cb2d0ff5f465ea2d00848a9a35dc name: tzdata evr: 2026c-1.el9_8 sourcerpm: tzdata-2026c-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/u/util-linux-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 2390152 checksum: sha256:3681bbe37d46309673f366135787f5713f0ef575e3cd413cd221af2512e3634b name: util-linux evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/u/util-linux-core-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 472949 checksum: sha256:de7ad826dd42b383d93f6dc6b720a26d4cd4815d00c0f093c2e28037a8881424 name: util-linux-core evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/v/vim-filesystem-8.2.2637-26.el9_8.21.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 24156 checksum: sha256:4362190912229b1a670e20a7e46d1f20fb2d1d30e80ec22342bba1318784f742 name: vim-filesystem evr: 2:8.2.2637-26.el9_8.21 sourcerpm: vim-8.2.2637-26.el9_8.21.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/x/xz-5.2.5-8.el9_0.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 235798 checksum: sha256:26ac21be6c1e396c7bcbaa9d4786e3275e996d9d78c01f75bbbc6962e6c9bef7 name: xz evr: 5.2.5-8.el9_0 sourcerpm: xz-5.2.5-8.el9_0.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/x/xz-libs-5.2.5-8.el9_0.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 94569 checksum: sha256:06931afb372ed4a6893e51558beaa6b0eab7adda0af93456fd99a081a8b80779 name: xz-libs evr: 5.2.5-8.el9_0 sourcerpm: xz-5.2.5-8.el9_0.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/z/zlib-1.2.11-40.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 94454 checksum: sha256:2e7f193e67235130c10f5579c2d2ec92e22e4098b6d12fb2855d93b1540c60f7 name: zlib @@ -1052,1036 +1052,1036 @@ arches: - arch: x86_64 packages: - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cmake-3.31.8-3.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 13989883 checksum: sha256:e67ea7aef1edd470e4ec22982e97871655abcdc0990754d4e8f147d4e7de317a name: cmake evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cmake-data-3.31.8-3.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 2829291 checksum: sha256:1cdc2e88a996c575b750483c8f562674e4b50a6ab414c7bbe6f6b641c1db7bd9 name: cmake-data evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cmake-filesystem-3.31.8-3.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 19309 checksum: sha256:b5ea81385a9e4e6a1ae2bb1175cd774af82f9c570a37008cde873ead466ba5f7 name: cmake-filesystem evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cpp-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 11226193 checksum: sha256:3c0ee1cb8b72f3f5176f8945ab518eb733ccc9f950d317fb5d5ac327d0eb9c90 name: cpp evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/e/emacs-filesystem-27.2-18.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 9495 checksum: sha256:49d7b88a05a72c15b78191a987e6def04fda8e2e4ff75711f715d0c0ecadc60f name: emacs-filesystem evr: 1:27.2-18.el9 sourcerpm: emacs-27.2-18.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gawk-all-langpacks-5.1.0-6.el9_8.1.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 218134 checksum: sha256:bedaac1d71ca9f41e3398dc413dea2008bfd20799de1b4db14e37ebb7a7ce74d name: gawk-all-langpacks evr: 5.1.0-6.el9_8.1 sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 33982584 checksum: sha256:2082784165bbb246b6e5ef5921ed823f0a9709cacdf5823aa60695fd6d4819a2 name: gcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-c++-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 13474286 checksum: sha256:b073d8965ad8eed7520a2a1c9b7c961232511ad9188dcc8c92356160a9d51e37 name: gcc-c++ evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/git-core-2.52.0-1.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 5293286 checksum: sha256:6264aa556d583604f34def3674f5830a70cfee0aac283719e4df295db38acb53 name: git-core evr: 2.52.0-1.el9 sourcerpm: git-2.52.0-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/glibc-devel-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 46499 checksum: sha256:a3b6ed698d21192fa7c421094a5d6648411fba4252db28c96d629778c86e6cd5 name: glibc-devel evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/glibc-headers-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 567171 checksum: sha256:2124192aba2e7931cdf00c2dcd4b70b71b313790c28dcf09b2fb09cc9831c86f name: glibc-headers evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 2965145 checksum: sha256:2473df2cf5b65c762af7941fe87324e98dc0e8b42d1e5745051a0405e200b7f5 name: kernel-headers evr: 5.14.0-687.53.1.el9_8 sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 66075 checksum: sha256:b97b4e98c3c6f41dcfc2ceb4ffa1aba7a338b7cfd9e6c4f63e3160dd3cc033d3 name: libmpc evr: 1.2.1-4.el9 sourcerpm: libmpc-1.2.1-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libstdc++-devel-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 2524816 checksum: sha256:2d031d05fe073adc74919b8372763ae322615d9df23f4a2c642050ab4b389ce5 name: libstdc++-devel evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libtool-ltdl-2.4.6-46.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 38043 checksum: sha256:44f7303229bdb4c2975f9829e3dd13dc7984e2cb53ef0f85baf894b39f605c38 name: libtool-ltdl evr: 2.4.6-46.el9 sourcerpm: libtool-2.4.6-46.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libuv-1.42.0-2.el9_4.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 154427 checksum: sha256:e1fab39251239ccaad2fb4dbe6c55ec1ae60f76d4ae81582b06e6a58e30879b2 name: libuv evr: 1:1.42.0-2.el9_4 sourcerpm: libuv-1.42.0-2.el9_4.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libxcrypt-compat-4.4.18-3.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 93189 checksum: sha256:2bd6c288e1970a001d3a1ae69166c0d926d9c87ce892edcb2110f4e142c12a7a name: libxcrypt-compat evr: 4.4.18-3.el9 sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libxcrypt-devel-4.4.18-3.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 33101 checksum: sha256:c1d171391a7d2e043a6953efd3df3e01edc9b4c6cdb54517e1608d204a5fce18 name: libxcrypt-devel evr: 4.4.18-3.el9 sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/o/openssl-devel-3.5.8-1.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 5039851 checksum: sha256:8042a4b1134610ef06c27b7314a5fcd4ce887d1065d4b9e861bc3f647d7fb792 name: openssl-devel evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/p/python-unversioned-command-3.9.25-7.el9_8.3.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 15791 checksum: sha256:6942913da91f52227ca9b22c6bd4269f49682a807beb0380dbdafe8c0a51d86e name: python-unversioned-command evr: 3.9.25-7.el9_8.3 sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/a/acl-2.4.0-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 85269 checksum: sha256:611da2c85401a2f26dba165c0be27b2e62fa71ceb5c392c8f5a9d30c31238d5b name: acl evr: 2.4.0-1.el9_8 sourcerpm: acl-2.4.0-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/a/alternatives-1.24-2.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 42874 checksum: sha256:1c520b9bf7b592d936bb347a5107702e51678e160b88ecfbba6a30e35e47d24e name: alternatives evr: 1.24-2.el9 sourcerpm: chkconfig-1.24-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/a/audit-libs-3.1.5-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 130600 checksum: sha256:637ac2995ce1a6c222772b60f6bc6e6f2829355d2c88dfb1262fb76146d985ae name: audit-libs evr: 3.1.5-8.el9 sourcerpm: audit-3.1.5-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/basesystem-11-13.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 8229 checksum: sha256:f498b0813fa1a825d550e8e3a9e42255eabfa18e6fc96adfc6cc8fa7e16dd513 name: basesystem evr: 11-13.el9 sourcerpm: basesystem-11-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/bash-5.1.8-9.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1769540 checksum: sha256:d3adf8b09aa0bf935c67aa12444e0ee02f70a82c2682bfb2b02bda0a989bb806 name: bash evr: 5.1.8-9.el9 sourcerpm: bash-5.1.8-9.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/binutils-2.35.2-72.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 4821853 checksum: sha256:bda706d43bf47267e31db8ac62fe3206122f97ff035daa6c93ce9cd5063a63ca name: binutils evr: 2.35.2-72.el9 sourcerpm: binutils-2.35.2-72.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/binutils-gold-2.35.2-72.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 758393 checksum: sha256:4d429d1030d8e1c610ba5aea83f8c63090033f440b2c8f788f0e0acd4a3c51b3 name: binutils-gold evr: 2.35.2-72.el9 sourcerpm: binutils-2.35.2-72.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/bzip2-libs-1.0.8-11.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 46333 checksum: sha256:948f763ed17672b8dd83356541e27a53ce97c6df38339c4416a188d452ca4d1e name: bzip2-libs evr: 1.0.8-11.el9 sourcerpm: bzip2-1.0.8-11.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/ca-certificates-2025.2.80_v9.0.305-91.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1072208 checksum: sha256:0554bf65d573950e7d550a07bf7d0b3def85ca3b45a7fbde4cce7cadd9a476a7 name: ca-certificates evr: 2025.2.80_v9.0.305-91.el9 sourcerpm: ca-certificates-2025.2.80_v9.0.305-91.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/coreutils-8.32-41.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1222083 checksum: sha256:374257c4cd69107333a7f524dd99579f3ea3ab842b66168eade0a3475fb6eea1 name: coreutils evr: 8.32-41.el9_8.1 sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 2113503 checksum: sha256:41f69eb8b2087feaa98d0228fb933b6fe8af20a4bf371cfef51e11cbd1f84b4e name: coreutils-common evr: 8.32-41.el9_8.1 sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/cracklib-2.9.6-28.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 102444 checksum: sha256:3b415381d4bd307686268ec42f646c7770f6a815de73a40a88aab7a7061b30a9 name: cracklib evr: 2.9.6-28.el9 sourcerpm: cracklib-2.9.6-28.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/cracklib-dicts-2.9.6-28.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 3829431 checksum: sha256:61c11d3c23b62016b9939f917bb7f7e03cba324eb4ad35b375387ce9f18e25a1 name: cracklib-dicts evr: 2.9.6-28.el9 sourcerpm: cracklib-2.9.6-28.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/crypto-policies-20260224-1.gitea0f072.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 98707 checksum: sha256:a9ad86b8df75a7c9c29c6f6dd4b8f78cfa1f42b492eff0d11b82c457d37a7f9f name: crypto-policies evr: 20260224-1.gitea0f072.el9_8 sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/crypto-policies-scripts-20260224-1.gitea0f072.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 111065 checksum: sha256:cec44db94e9132008a7fe4e1aa764a29e9c125989c9ac2411f3cf6f8fb669dfd name: crypto-policies-scripts evr: 20260224-1.gitea0f072.el9_8 sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/cyrus-sasl-lib-2.1.27-22.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 786202 checksum: sha256:a85ebdee7a9a49990f87e4709c368212e6a54ecf18c88a3dd54d823a82443898 name: cyrus-sasl-lib evr: 2.1.27-22.el9 sourcerpm: cyrus-sasl-2.1.27-22.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/d/dbus-1.12.20-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 8073 checksum: sha256:96b1daa4de0a635ab760a8431fb005022bb7cb48d2d1d3ec9a8adb1798c0e10e name: dbus evr: 1:1.12.20-8.el9 sourcerpm: dbus-1.12.20-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/d/dbus-broker-28-9.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 180434 checksum: sha256:2c3b853f8548394af581f487d3682e3e6a4fd27bb451088ce278c7f00af454db name: dbus-broker evr: 28-9.el9_8 sourcerpm: dbus-broker-28-9.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/d/dbus-common-1.12.20-8.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 18551 checksum: sha256:298f1cada3cbcef6713098b9925694a0e30e8566f7a5bdbd72384520cf6c8360 name: dbus-common evr: 1:1.12.20-8.el9 sourcerpm: dbus-1.12.20-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-debuginfod-client-0.194-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 43826 checksum: sha256:1635fd1ecaa9492fa925956dfd56d10063ca336619218f124ab45df0a38b41b0 name: elfutils-debuginfod-client evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-default-yama-scope-0.194-1.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 8949 checksum: sha256:c473f42c0fec97e2830580110509e4522bc62d9b11761d062499354a0bf58959 name: elfutils-default-yama-scope evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-libelf-0.194-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 205864 checksum: sha256:00bbe4776149d8ccedcdf19dd6ceb08c3bb42ff41b98d744abe101af0b11a6c5 name: elfutils-libelf evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-libs-0.194-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 274670 checksum: sha256:ad4f6d425cbc975cead56a2c982e38b0146d00944cbd9b3072d3d1f1271237a5 name: elfutils-libs evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.5.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 129088 checksum: sha256:e6e7edd632fdd1dc4ad94b19b7ab88646f3ef8c1c7956bac58985c3e7118a362 name: expat evr: 2.5.0-6.el9_8.5 sourcerpm: expat-2.5.0-6.el9_8.5.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/f/filesystem-3.16-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 5003807 checksum: sha256:9567592e6e32a9ebd45584cc4feb5d00812f143fcb2d8cd8b1d95108f4f66a2d name: filesystem evr: 3.16-5.el9 sourcerpm: filesystem-3.16-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/f/findutils-4.8.0-7.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 563531 checksum: sha256:a6328afea0a11647b7fb5c48436f0af6c795407bac0650676d3196dd47070de6 name: findutils evr: 1:4.8.0-7.el9 sourcerpm: findutils-4.8.0-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gawk-5.1.0-6.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1046649 checksum: sha256:fcc5e724c32597cf781626728f0faff2f0e5ed6455ab95af4883eefca30a074e name: gawk evr: 5.1.0-6.el9_8.1 sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 60152 checksum: sha256:c8b8346a98d921206666ce740a3647a52ad7a87c2d01d73166165b3e9a789a6c name: gdbm-libs evr: 1:1.23-1.el9 sourcerpm: gdbm-1.23-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 2083064 checksum: sha256:7d2d420b97c05c09ee1e9bd881cb0725fe8d89688b933f411f278cb23210c65d name: glibc evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-common-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 321585 checksum: sha256:f23581b888783f576bd3a55503618a48f74f468fcfd4837bbd7a2d00fe7f3530 name: glibc-common evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-gconv-extra-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1763577 checksum: sha256:91899acba57caeb8768bf2ce4631d7f56c6b8e052f2bdf20003382205eb8eee8 name: glibc-gconv-extra evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-minimal-langpack-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 30913 checksum: sha256:6cc48d78bf2ceacfa5b58633b648c564b66505f4677adea8eb663fe90acd76f2 name: glibc-minimal-langpack evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gmp-6.2.0-13.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 326840 checksum: sha256:d4529445e30b7eb9a8225b0539f70d26d585d7fe306296f948ea73114d1c171f name: gmp evr: 1:6.2.0-13.el9 sourcerpm: gmp-6.2.0-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/grep-3.6-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 279174 checksum: sha256:5556895ff1817066ca71b50785615e944b0fcc7e1c94c983087c7c691819623d name: grep evr: 3.6-5.el9 sourcerpm: grep-3.6-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gzip-1.12-2.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 172571 checksum: sha256:a87bdcce45011f232758c01bd99c564b5f6b549d391646cc573a132d22604b85 name: gzip evr: 1.12-2.el9_8 sourcerpm: gzip-1.12-2.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/j/json-c-0.14-11.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 46136 checksum: sha256:b9bde4162250023103d95908fbca44fff6636a46176f92cf1761c1c3a4580a2f name: json-c evr: 0.14-11.el9 sourcerpm: json-c-0.14-11.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/k/keyutils-libs-1.6.3-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 34363 checksum: sha256:96d75824948387a884d206865db534cd3d46f32422efcb020c20060b59edb27c name: keyutils-libs evr: 1.6.3-1.el9 sourcerpm: keyutils-1.6.3-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/k/kmod-libs-28-11.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 63619 checksum: sha256:f296bc24a1b8ba6c40ed73ba736be97ed78e4124b6dbdd8a0a25a9683d4ff1ce name: kmod-libs evr: 28-11.el9 sourcerpm: kmod-28-11.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/k/krb5-libs-1.21.1-10.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 790743 checksum: sha256:8906be9f2d414c5f4e1218db0c94955c2b3394b43a04b7dbcc2ef66c4340ebc6 name: krb5-libs evr: 1.21.1-10.el9_8 sourcerpm: krb5-1.21.1-10.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/less-590-6.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 166025 checksum: sha256:5bd040f9dd813167935fc390d546c119d90e0a9c77447a3d9ed1ef69c6f5a32a name: less evr: 590-6.el9 sourcerpm: less-590-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libacl-2.4.0-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 31657 checksum: sha256:a81fb7a4d7c946e9bd886ee3c471a4b5040dedbb8ffb2a388120b2094be93cc8 name: libacl evr: 2.4.0-1.el9_8 sourcerpm: acl-2.4.0-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libarchive-3.5.3-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 402853 checksum: sha256:3b8ed4523d8721a1fabc2c92e6871c353b8ff5fb555663ab006fe90a7be35a86 name: libarchive evr: 3.5.3-11.el9_8 sourcerpm: libarchive-3.5.3-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libattr-2.6.0-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 23752 checksum: sha256:9e37537f690c748f7f05faa80966072f118ec722e38ef332fe19cf22b087349f name: libattr evr: 2.6.0-1.el9_8 sourcerpm: attr-2.6.0-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libblkid-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 114192 checksum: sha256:a858400abe83a7955ae509c920f835a950ea2cf1156916823c595a23ba46d536 name: libblkid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libbrotli-1.0.9-9.el9_7.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 326278 checksum: sha256:81096e6aed022489306e2fe1d1496b2b689d8f0bf6c70a94b5bddb82356eeda1 name: libbrotli evr: 1.0.9-9.el9_7 sourcerpm: brotli-1.0.9-9.el9_7.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcap-2.48-10.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 78928 checksum: sha256:d4805439b10fa551b7535cf30ca28d4d5862132c9c429b2b31221bea7f43263a name: libcap evr: 2.48-10.el9_8.1 sourcerpm: libcap-2.48-10.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcap-ng-0.8.2-7.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 36752 checksum: sha256:ebddfc188d1ddbb0d6a238583cbc02dcb9fc0bd063a850b22d48980899976628 name: libcap-ng evr: 0.8.2-7.el9 sourcerpm: libcap-ng-0.8.2-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcbor-0.7.0-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 60575 checksum: sha256:588e8736af3376abfb3cdf372c10baef02c40d916a55958f3bee9767f9ad8526 name: libcbor evr: 0.7.0-5.el9 sourcerpm: libcbor-0.7.0-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcom_err-1.46.5-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 26980 checksum: sha256:b7593ee2d841c69573d8ed553b7416ef727b2c77c0473416a5dadf4b567bf547 name: libcom_err evr: 1.46.5-8.el9 sourcerpm: e2fsprogs-1.46.5-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.7.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 296930 checksum: sha256:044d7a379f0f0f6ed25b9d41439dedfb55b390108ae5a09d4acc6b3565c39ae1 name: libcurl evr: 7.76.1-40.el9_8.7 sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libdb-5.3.28-57.el9_6.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 755192 checksum: sha256:3246e76f197e2b60eb470b9b55d3e0dda2301b029f295fed9c38ff70b87c5b6b name: libdb evr: 5.3.28-57.el9_6 sourcerpm: libdb-5.3.28-57.el9_6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libeconf-0.4.1-7.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 33179 checksum: sha256:a570c5baaedeb1445bc2e4f3930b0a709411bbefbdbf463c3e006cbfc7018894 name: libeconf evr: 0.4.1-7.el9_8 sourcerpm: libeconf-0.4.1-7.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libedit-3.1-39.20210216cvs.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 112056 checksum: sha256:65a730688dfea27934b75af3acf30150c6d254c89d8b68b63233ae7f8b6c9b94 name: libedit evr: 3.1-39.20210216cvs.el9 sourcerpm: libedit-3.1-39.20210216cvs.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libevent-2.1.13-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 276162 checksum: sha256:c28b28573118d5cf0f1f68d96bc7c46d56671e45360698c894ca06ddf40163ae name: libevent evr: 2.1.13-1.el9_8 sourcerpm: libevent-2.1.13-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libfdisk-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 161769 checksum: sha256:1b861f267752e718ce696a80dcc06ef1dde8e9aa73fa2abed3775626d719c124 name: libfdisk evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libffi-3.4.2-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 40619 checksum: sha256:dde0012a94c6f3825e605b095b15767d89c2b87a5da097348310d7e87721c645 name: libffi evr: 3.4.2-8.el9 sourcerpm: libffi-3.4.2-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libfido2-1.13.0-2.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 102746 checksum: sha256:6da940c0528f3e4453db84cb85b402c8f4293a197b1921158df9651edb4845e0 name: libfido2 evr: 1.13.0-2.el9 sourcerpm: libfido2-1.13.0-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgcc-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 87280 checksum: sha256:77c66827ffc14df2f43612b26128b1fd58d5c9597d4d4e564aa239b161272872 name: libgcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgcrypt-1.10.0-13.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 523829 checksum: sha256:c07cd9f613809195b8691d28fd2f3bff55b2a83b9c1cdf4a32c681e0e32dfafb name: libgcrypt evr: 1.10.0-13.el9_8 sourcerpm: libgcrypt-1.10.0-13.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgomp-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 263723 checksum: sha256:87b9a7316760374111290e6e4c76ee4d093566f08a7c7c91ad7827c5948afb69 name: libgomp evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgpg-error-1.42-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 225603 checksum: sha256:8248e20d7a253aa9c0dc7dc3d56b42e1def4fd5753ce8e8b9e980aa664fc9068 name: libgpg-error evr: 1.42-5.el9 sourcerpm: libgpg-error-1.42-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libidn2-2.3.0-7.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 107099 checksum: sha256:055f4ce6b721be7138dc2e45a6586412c65508acea3fe385a2655c129fe264f9 name: libidn2 evr: 2.3.0-7.el9 sourcerpm: libidn2-2.3.0-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libmount-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 142467 checksum: sha256:a9a2022eb9e39301bfcd3273573a108486b2b315c89247f147870016b2e9222c name: libmount evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libnghttp2-1.43.0-6.el9_8.2.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 80410 checksum: sha256:adb3260b6610917c07bda0a6bc521d5628bb1892a66d008f0fea81dc022ac699 name: libnghttp2 evr: 1.43.0-6.el9_8.2 sourcerpm: nghttp2-1.43.0-6.el9_8.2.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libpkgconf-1.7.3-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 38387 checksum: sha256:4feae5941b73640bd86b8d506a657cac5b770043db1464fbcd207721b2159dda name: libpkgconf evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libpsl-0.21.1-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 67454 checksum: sha256:ad1a62ef07682bb64a476c1a49f5cfc7abc9beb44775e7e511bf737e9a6bf99d name: libpsl evr: 0.21.1-5.el9 sourcerpm: libpsl-0.21.1-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libpwquality-1.4.4-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 126104 checksum: sha256:14b7ff2f7fdaf8ebec90261f4619ea7f7c3564c4de8483666de7ed4b1f49b66f name: libpwquality evr: 1.4.4-8.el9 sourcerpm: libpwquality-1.4.4-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libseccomp-2.5.2-2.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 76200 checksum: sha256:e2015f60dbe784330d5df43f3f05c68c307694600a636a1706bf86527cc82e82 name: libseccomp evr: 2.5.2-2.el9 sourcerpm: libseccomp-2.5.2-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libselinux-3.6-3.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 89722 checksum: sha256:ce1cc63a7212c39f5f2a35f719ee38d6418cf081ea78c9317f388d9f41e4a627 name: libselinux evr: 3.6-3.el9 sourcerpm: libselinux-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsemanage-3.6-5.el9_6.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 123449 checksum: sha256:7ac29f46714cd762f18a52e9807fd1766b0cf9e0388aa3d9befaabf8785a01e3 name: libsemanage evr: 3.6-5.el9_6 sourcerpm: libsemanage-3.6-5.el9_6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsepol-3.6-3.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 338766 checksum: sha256:b98984b2bf42203964cc979ac157df090c63b89a0f5c6560ede01965531c8ffd name: libsepol evr: 3.6-3.el9 sourcerpm: libsepol-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsigsegv-2.13-4.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 30681 checksum: sha256:24005c62017797b612d047a2af83a218633b32302a787fabd22e52230db6adc1 name: libsigsegv evr: 2.13-4.el9 sourcerpm: libsigsegv-2.13-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsmartcols-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 68692 checksum: sha256:c1da912799780b89cd44db4acc318ea4a83adba0d8d99aad1b877879f40dbd48 name: libsmartcols evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libssh-0.10.4-19.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 225821 checksum: sha256:10d0ecb7cef182f8d11eb4bc772943a60c48b8171f602ffd83bb79b9edf5eb44 name: libssh evr: 0.10.4-19.el9_8 sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libssh-config-0.10.4-19.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 14764 checksum: sha256:ce06b99793b9e5abc50a58ebc3ef11037f2efbdb5f819a25f8de4994613194d4 name: libssh-config evr: 0.10.4-19.el9_8 sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libstdc++-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 763021 checksum: sha256:513df35338962e053052b9d52429e8a5f3d60dfe6b1757cd9faaf61d6abda955 name: libstdc++ evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libtasn1-4.16.0-10.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 81418 checksum: sha256:f9473f322407f10205b0db98b89cf8f603e9c769e9977250734136df56cbb981 name: libtasn1 evr: 4.16.0-10.el9_8 sourcerpm: libtasn1-4.16.0-10.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libunistring-0.9.10-15.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 510558 checksum: sha256:6477fb3c3285158f676360e228057e13dc6e983f453c7c74ed4ab140357f9a0d name: libunistring evr: 0.9.10-15.el9 sourcerpm: libunistring-0.9.10-15.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libutempter-1.2.1-6.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 30354 checksum: sha256:0f1df5e0d48c2ac9914bfffa7ed569cd58e42b17ba96bb3f7cf74d1e80de2597 name: libutempter evr: 1.2.1-6.el9 sourcerpm: libutempter-1.2.1-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libuuid-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 32757 checksum: sha256:5694aafca42c707f85af66bba11d102f7636ee17f586466b3ff80254e995ed7b name: libuuid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libverto-0.3.2-3.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 25042 checksum: sha256:7008029afd91af33ca17a22e6eb4ba792fd9b32bee8fb613c79c1527fa6f589a name: libverto evr: 0.3.2-3.el9 sourcerpm: libverto-0.3.2-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libxcrypt-4.4.18-3.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 122599 checksum: sha256:a50bb26a28ee7e6379c86b5b91285299b71569fa87ea968d800a56090b7a179d name: libxcrypt evr: 4.4.18-3.el9 sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libxml2-2.9.13-14.el9_8.5.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 773693 checksum: sha256:d55744c4fe83a63a71906ca41607c5c0deff227a69b957708cc6a37537be4a29 name: libxml2 evr: 2.9.13-14.el9_8.5 sourcerpm: libxml2-2.9.13-14.el9_8.5.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libzstd-1.5.5-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 304135 checksum: sha256:d8a149f0d8f217126642cc4b40199d631b940f7d227191cc2179f3158fd47f9e name: libzstd evr: 1.5.5-1.el9 sourcerpm: zstd-1.5.5-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/lz4-libs-1.9.3-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 70922 checksum: sha256:9658da838021711f687cf283368664984bfb1c8b9176897d7d477a724a11a731 name: lz4-libs evr: 1.9.3-5.el9 sourcerpm: lz4-1.9.3-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/m/make-4.3-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 553896 checksum: sha256:561f0c2251e9217c81a6c88de4d2d9231a039aaab37e8a0d2559d36ce9fa85fd name: make evr: 1:4.3-8.el9 sourcerpm: make-4.3-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/m/mpfr-4.1.0-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 338130 checksum: sha256:4adb12cda3b0e537ba5b22e7615288df751720d2e738bd182675d529cf1ead0c name: mpfr evr: 4.1.0-10.el9 sourcerpm: mpfr-4.1.0-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/n/ncurses-base-6.2-12.20210508.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 97840 checksum: sha256:d62dfd41f9688efa2cf1ceedb96084c63e297fbdcfd1e72bc6757c730092b60c name: ncurses-base evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/n/ncurses-libs-6.2-12.20210508.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 336270 checksum: sha256:f3e1f8e59c7116278aa19b6705a1443f6307d4d6fbdde75a23d2f5d60636cb16 name: ncurses-libs evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openldap-2.6.8-4.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 296805 checksum: sha256:68df8cf8fb4d54c2f1681fa9a030f7af3b179e6dd4fd10ffd7532824121ea74c name: openldap evr: 2.6.8-4.el9 sourcerpm: openldap-2.6.8-4.el9.src.rpm - - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssh-9.9p1-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms - size: 443050 - checksum: sha256:9fad2dc75044e577f03442e524b58223239eba14b12adbf8d14eeb82d22b596e + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssh-9.9p1-12.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 443141 + checksum: sha256:12db3094d78ed82bd7edc6a7cdd2cbf5198da695e293b47b5d00c31ac142aeb7 name: openssh - evr: 9.9p1-11.el9_8 - sourcerpm: openssh-9.9p1-11.el9_8.src.rpm - - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssh-clients-9.9p1-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms - size: 799504 - checksum: sha256:445f8ef1a60229d575547695da0bf2a05fb51408bce01d06548a5273123c8877 + evr: 9.9p1-12.el9_8 + sourcerpm: openssh-9.9p1-12.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssh-clients-9.9p1-12.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 800429 + checksum: sha256:a4307b914ec45f69fea0e17b593ae61597db3e2796ce01a8809c55173f30c121 name: openssh-clients - evr: 9.9p1-11.el9_8 - sourcerpm: openssh-9.9p1-11.el9_8.src.rpm + evr: 9.9p1-12.el9_8 + sourcerpm: openssh-9.9p1-12.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-3.5.8-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1569041 checksum: sha256:2e0d5017032a48c23d4efa43afbe98d45179365eaec16f38e93c271b7728a67a name: openssl evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 14256 checksum: sha256:c00860e9c5a1d90488aa2eb65fe41f62926b38c6b3669d331a8b97e4a60223ac name: openssl-fips-provider evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-fips-provider-so-3.0.7-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 595008 checksum: sha256:60d36ad3a67d6b00e67bb0a19c0902fbb2ecdd873cf7f280a3039d04a092790c name: openssl-fips-provider-so evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-libs-3.5.8-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 2430624 checksum: sha256:3abe1f190415d91d4dc51db74cf2ad7e06b1e5ca5f63dac978fe58d8fd14e493 name: openssl-libs evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/p11-kit-0.26.4-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 625862 checksum: sha256:a00ba14bfd0fc5dd2818f605f2e0520b52ea4b36167504c2523f92a065c2bdaf name: p11-kit evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/p11-kit-trust-0.26.4-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 165521 checksum: sha256:41b84ab0ee4cf914d570a3d648ed98b7de44cef73ea3dfa58ff5eebdec85f42a name: p11-kit-trust evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pam-1.5.1-28.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 645147 checksum: sha256:9285aea93392dada0c8399b7d8a1c85ce7a6fdafd2fb5264727448549c15637c name: pam evr: 1.5.1-28.el9_8.1 sourcerpm: pam-1.5.1-28.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pcre-8.44-4.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 205261 checksum: sha256:e9ddc7d57d4f6e7400b66bcc78b9bafc1f05630e3e0d2a14000bc907f429ddc4 name: pcre evr: 8.44-4.el9 sourcerpm: pcre-8.44-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pcre2-10.40-6.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 241900 checksum: sha256:75db1e5a50e7b1794d7ba18212d95cd2684559da9e7c52eee46490302c7f24dd name: pcre2 evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pcre2-syntax-10.40-6.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 147926 checksum: sha256:d386b5e9b3a4b077b2ba143882e605750855dd3354f13c55fa12ed26908cb442 name: pcre2-syntax evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pkgconf-1.7.3-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 45675 checksum: sha256:bb47b4ecc499c308f41031a99e723827d152d5d750f59849d0c265d820944a26 name: pkgconf evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pkgconf-m4-1.7.3-10.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 16054 checksum: sha256:91bafd6e06099451f60288327b275cfcc651822f6145176a157c6b0fa5131e02 name: pkgconf-m4 evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pkgconf-pkg-config-1.7.3-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 12438 checksum: sha256:9a502d81d73d3303ceb53a06ad7ce525c97117ea64352174a33708bf3429283d name: pkgconf-pkg-config evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/publicsuffix-list-dafsa-20210518-3.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 60882 checksum: sha256:e6ec3390a736b085f403168c512a6b2b6f8e12a8fd5a4459f1c7dbbff2b67c33 name: publicsuffix-list-dafsa evr: 20210518-3.el9 sourcerpm: publicsuffix-list-20210518-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-3.9.25-7.el9_8.3.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 33200 checksum: sha256:f0d622eb17a038e98c23ef9bd6961c104c55b3534f69c301b2067cd9161f40f7 name: python3 evr: 3.9.25-7.el9_8.3 sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-libs-3.9.25-7.el9_8.3.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 8483866 checksum: sha256:6227afce7123d2e6c46a4a6d318087c512caebf6e09f7bfc0ba73f7e5853fba4 name: python3-libs evr: 3.9.25-7.el9_8.3 sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-pip-wheel-21.3.1-2.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1198443 checksum: sha256:918041963ad5c3b91276bf1ed3307280673ca8352e2e632bbb3847b33d2bc15a name: python3-pip-wheel evr: 21.3.1-2.el9_8 sourcerpm: python-pip-21.3.1-2.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-setuptools-wheel-53.0.0-15.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 479203 checksum: sha256:36dacb345e21bc0308ef2508f0c93995520a15ef0b56aab3593186c8dc9c0c5a name: python3-setuptools-wheel evr: 53.0.0-15.el9 sourcerpm: python-setuptools-53.0.0-15.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/r/readline-8.1-4.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 220174 checksum: sha256:01bf315b3bc44c28515c4d33d49173b23d7979d2a09b7b15f749d434b60851e6 name: readline evr: 8.1-4.el9 sourcerpm: readline-8.1-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/r/redhat-release-9.8-1.0.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 61742 checksum: sha256:8157ed988fc34dcfeb6429272959471edd5bde4ac212f26611fd54c180391758 name: redhat-release evr: 9.8-1.0.el9 sourcerpm: redhat-release-9.8-1.0.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/sed-4.8-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 317456 checksum: sha256:45e246453dc9eb1bad6a71c6f349aad1b1b2e1bf3ec645b80f0ed04fe69c960e name: sed evr: 4.8-10.el9 sourcerpm: sed-4.8-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/setup-2.13.7-10.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 153791 checksum: sha256:0891d395ce067121c28932534237ad1ce231f2bfa987411ad62e73a12d11eb6a name: setup evr: 2.13.7-10.el9 sourcerpm: setup-2.13.7-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/shadow-utils-4.9-16.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1250179 checksum: sha256:17294ee3fbc09c1b5cfc4114d3815cbd92584d6d70a6288e1dce2fda0a62dc59 name: shadow-utils evr: 2:4.9-16.el9 sourcerpm: shadow-utils-4.9-16.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/sqlite-libs-3.34.1-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 665095 checksum: sha256:e5c20e933ec01f746a6c59a94cb99f46c6138dab3f387f0d02dab47f356e2e98 name: sqlite-libs evr: 3.34.1-11.el9_8 sourcerpm: sqlite-3.34.1-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/systemd-252-67.el9_8.6.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 4383786 checksum: sha256:025ffe42235a9d4d40138bab7511995392d2a50ba6912be97b47e634b15823b8 name: systemd evr: 252-67.el9_8.6 sourcerpm: systemd-252-67.el9_8.6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/systemd-libs-252-67.el9_8.6.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 662007 checksum: sha256:d491d7375ed820bf4fa0d5f6d80a6f1a72cfa6ff31e79dabf9288246061cf88e name: systemd-libs evr: 252-67.el9_8.6 sourcerpm: systemd-252-67.el9_8.6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/systemd-pam-252-67.el9_8.6.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 258340 checksum: sha256:eb780b309cd671d4b3bec7d3b31bf148179e527d7222663544470310f4cb43ee name: systemd-pam evr: 252-67.el9_8.6 sourcerpm: systemd-252-67.el9_8.6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/systemd-rpm-macros-252-67.el9_8.6.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 40844 checksum: sha256:974a0352427799559a3e90dcdb38200d767631452cca7717c5b0fbe7760df313 name: systemd-rpm-macros evr: 252-67.el9_8.6 sourcerpm: systemd-252-67.el9_8.6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/t/tar-1.34-13.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 914200 checksum: sha256:913d84cd94463e3f0400d80c6742a2974eeab6445ac71ff9eb802a70779c146f name: tar evr: 2:1.34-13.el9_8 sourcerpm: tar-1.34-13.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 933286 checksum: sha256:8d6196e02853c0900c3c2e2367665beffb62cb2d0ff5f465ea2d00848a9a35dc name: tzdata evr: 2026c-1.el9_8 sourcerpm: tzdata-2026c-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/u/util-linux-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 2382511 checksum: sha256:35e0b73e574a7d8e93af0adf54adb2303f03423fd5761e357df86288f59d7933 name: util-linux evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/u/util-linux-core-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 476811 checksum: sha256:5575c8fc753d5a81022786dac33e172a6d1602ef41d247a58465754d3f952726 name: util-linux-core evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/v/vim-filesystem-8.2.2637-26.el9_8.21.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 24156 checksum: sha256:4362190912229b1a670e20a7e46d1f20fb2d1d30e80ec22342bba1318784f742 name: vim-filesystem evr: 2:8.2.2637-26.el9_8.21 sourcerpm: vim-8.2.2637-26.el9_8.21.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/x/xz-5.2.5-8.el9_0.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 235693 checksum: sha256:f16d17c26a241400586ddc3d734ce863e3f19d433881ec640a47bedf0dafd07b name: xz evr: 5.2.5-8.el9_0 sourcerpm: xz-5.2.5-8.el9_0.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/x/xz-libs-5.2.5-8.el9_0.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 96649 checksum: sha256:de263f880a4394f04b5e84254ba0a88d781b5bd63665c9e028bc10351490c982 name: xz-libs evr: 5.2.5-8.el9_0 sourcerpm: xz-5.2.5-8.el9_0.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/z/zlib-1.2.11-40.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 95708 checksum: sha256:baf95ffbf40ee014135f16fe33e343faf7ff1ca06509fd97cd988e6afeabf670 name: zlib diff --git a/deploy/konflux/gateway/rpms.lock.yaml b/deploy/konflux/gateway/rpms.lock.yaml index b3f74701e9..166160031d 100644 --- a/deploy/konflux/gateway/rpms.lock.yaml +++ b/deploy/konflux/gateway/rpms.lock.yaml @@ -5,1365 +5,1365 @@ arches: - arch: aarch64 packages: - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/checkpolicy-3.6-1.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 360096 checksum: sha256:be68ea8774a5aa3b0220043aa21aa93213d4ec0e1cf0c1e9114369ed16ada37e name: checkpolicy evr: 3.6-1.el9 sourcerpm: checkpolicy-3.6-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/clang-21.1.8-2.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 7554569 checksum: sha256:cb1188aa89484232468c58a1296fa61de218744eb75b22e150b477d60d231f97 name: clang evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/clang-devel-21.1.8-2.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 4494280 checksum: sha256:06192b7daf820f880f8feb8842928e07d88df155cffbb3332f6ed4ce872a7c3a name: clang-devel evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/clang-libs-21.1.8-2.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 31039242 checksum: sha256:12caacb5a1ca774ad9a41179d74c721e8243a043a48c0d286ea720394c724a83 name: clang-libs evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/clang-resource-filesystem-21.1.8-2.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 25797 checksum: sha256:5087b19492047a7139b1f1144e40d5091dcee5c95f35fa354781039b5c756a0d name: clang-resource-filesystem evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/clang-tools-extra-21.1.8-2.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 19364965 checksum: sha256:8ba62cb3611971be4a01354c067b51b8c73787fb64958cafd2e77989db9e5d46 name: clang-tools-extra evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cmake-3.31.8-3.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 11655593 checksum: sha256:2a77fe1c3784083dcdebdd548c16d823b3e4a5adb8d6c00e36841aa633eab53b name: cmake evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cmake-data-3.31.8-3.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 2829291 checksum: sha256:1cdc2e88a996c575b750483c8f562674e4b50a6ab414c7bbe6f6b641c1db7bd9 name: cmake-data evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cmake-filesystem-3.31.8-3.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 19282 checksum: sha256:69a498723354740357fc3f4b4cd235da38fadd98835da193bec0c0850f68f3ad name: cmake-filesystem evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cpp-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 10798392 checksum: sha256:eb16ef369b981c0dca6149e971a63f74ea09c09f1c638086e3cda1e0591ac21b name: cpp evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/e/emacs-filesystem-27.2-18.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 9495 checksum: sha256:49d7b88a05a72c15b78191a987e6def04fda8e2e4ff75711f715d0c0ecadc60f name: emacs-filesystem evr: 1:27.2-18.el9 sourcerpm: emacs-27.2-18.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 31291354 checksum: sha256:542e635ac17b548cc03ab4347438d49f96837c1d91d12714b6b2764ec566156c name: gcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-c++-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 12994215 checksum: sha256:aabe892798a2272d65c269fd6aa14d3b3dfc782c98f92f8fda30c2a4fa33bf6d name: gcc-c++ evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-toolset-14-14.0-2.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 10516 checksum: sha256:176ce522a37ef114051b114969cc72bf3da8ecb697368a18260fcf58eeee4bb2 name: gcc-toolset-14 evr: 14.0-2.el9 sourcerpm: gcc-toolset-14-14.0-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-toolset-14-annobin-docs-12.88-1.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 89636 checksum: sha256:80d5c6c300561524fb2f855fce1867546d4452ea5c72cfb5d53f2117153e9805 name: gcc-toolset-14-annobin-docs evr: 12.88-1.el9 sourcerpm: gcc-toolset-14-annobin-12.88-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-toolset-14-annobin-plugin-gcc-12.88-1.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 1001441 checksum: sha256:1e671eaaa6265094d89aaed03dfef45bc780619e62de032dafd24f70146f1304 name: gcc-toolset-14-annobin-plugin-gcc evr: 12.88-1.el9 sourcerpm: gcc-toolset-14-annobin-12.88-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-toolset-14-binutils-2.41-6.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 7357899 checksum: sha256:e23944e5ba23c055df25efebe7c3fe848755225b2441923110c519165b543ecf name: gcc-toolset-14-binutils evr: 2.41-6.el9 sourcerpm: gcc-toolset-14-binutils-2.41-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-toolset-14-dwz-0.14-1.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 128077 checksum: sha256:f4bcee0025b49e9653dbb3d939140d4fc9b6b8734156ebca2fc7b3c70d5e89bd name: gcc-toolset-14-dwz evr: 0.14-1.el9 sourcerpm: gcc-toolset-14-dwz-0.14-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-toolset-14-gcc-14.2.1-13.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 44178202 checksum: sha256:759df2069e08b4725204c86e1c367316af41adb713233876c1c323f0e383e395 name: gcc-toolset-14-gcc evr: 14.2.1-13.el9 sourcerpm: gcc-toolset-14-gcc-14.2.1-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-toolset-14-gcc-c++-14.2.1-13.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 13751718 checksum: sha256:4c1232051eaefabe1258ca799a80837a696637e8ac5e3b1ca54ac0d5f3f36394 name: gcc-toolset-14-gcc-c++ evr: 14.2.1-13.el9 sourcerpm: gcc-toolset-14-gcc-14.2.1-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-toolset-14-gcc-gfortran-14.2.1-13.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 13520354 checksum: sha256:2a8e9ccfdde357bc2ecc47ae1d1631d3e07945b4cd44a9fb6b08e8d40795fd6f name: gcc-toolset-14-gcc-gfortran evr: 14.2.1-13.el9 sourcerpm: gcc-toolset-14-gcc-14.2.1-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-toolset-14-libstdc++-devel-14.2.1-13.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 3783154 checksum: sha256:50354426d57fa9c93b656a51f97c72a39fe662dd7e694ec6d2865c527186ae88 name: gcc-toolset-14-libstdc++-devel evr: 14.2.1-13.el9 sourcerpm: gcc-toolset-14-gcc-14.2.1-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-toolset-14-runtime-14.0-2.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 58526 checksum: sha256:afe549f07bbaf06f2619d47b18155eaa1f8416dadbff5cbeb99cf17033d7ac25 name: gcc-toolset-14-runtime evr: 14.0-2.el9 sourcerpm: gcc-toolset-14-14.0-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-toolset-15-binutils-2.44-5.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 6565787 checksum: sha256:80da34e7f0ab8f9239fc71e43f2f75f75e4cb9bcdc5d89b9a8b54b96bc04acee name: gcc-toolset-15-binutils evr: 2.44-5.el9 sourcerpm: gcc-toolset-15-binutils-2.44-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-toolset-15-gcc-15.2.1-7.1.el9_8.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 48931221 checksum: sha256:a98dbc56b02c315701efab486373b361126e471fbb792729511da4b26ebd2340 name: gcc-toolset-15-gcc evr: 15.2.1-7.1.el9_8 sourcerpm: gcc-toolset-15-gcc-15.2.1-7.1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-toolset-15-gcc-c++-15.2.1-7.1.el9_8.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 15465405 checksum: sha256:9b5c6d7dbe8d479062b4ae3847aa3e23add35ce496bafe7759b5678b91587211 name: gcc-toolset-15-gcc-c++ evr: 15.2.1-7.1.el9_8 sourcerpm: gcc-toolset-15-gcc-15.2.1-7.1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-toolset-15-libstdc++-devel-15.2.1-7.1.el9_8.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 3953483 checksum: sha256:fef924530e3a52b2fb599efd4d88dd338e3c50ab8906af5677c9c0dc96fde128 name: gcc-toolset-15-libstdc++-devel evr: 15.2.1-7.1.el9_8 sourcerpm: gcc-toolset-15-gcc-15.2.1-7.1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-toolset-15-runtime-15.0-9.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 54344 checksum: sha256:4ea3558c3d09059ee9cefa2fca97fc7f127e8b479f30c64445a095c1cf5ce4fc name: gcc-toolset-15-runtime evr: 15.0-9.el9 sourcerpm: gcc-toolset-15-15.0-9.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/git-core-2.52.0-1.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 5392428 checksum: sha256:7082917982981bf611c089e7d9d53b63e969af61a37cfd65b5c4081d5b260a1c name: git-core evr: 2.52.0-1.el9 sourcerpm: git-2.52.0-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/glibc-devel-2.34-275.el9_8.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 576947 checksum: sha256:aef79b53955c2ec67efdc39cb91148b377f8719a8b68cf76c7ddcffbcb6b8bf0 name: glibc-devel evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 2925601 checksum: sha256:7e82c856a00206976aede9e7b3865d0a2363b1262c0f9e1949ba29c7b9c47281 name: kernel-headers evr: 5.14.0-687.53.1.el9_8 sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libasan-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 409047 checksum: sha256:854a84e72d40c2a062d112b93f8a694044fd7dc5b3afe7a869a448a12844c3e6 name: libasan evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libedit-devel-3.1-39.20210216cvs.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 54302 checksum: sha256:7f13c7aa8cf52c8f64c0c4e64b821d119ca9e3c4844ed8b24b377cbd90a4db33 name: libedit-devel evr: 3.1-39.20210216cvs.el9 sourcerpm: libedit-3.1-39.20210216cvs.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 67120 checksum: sha256:3763354a5f45d886f9976eec20eb34f8afc2144c69ffba07de546f2820893c70 name: libmpc evr: 1.2.1-4.el9 sourcerpm: libmpc-1.2.1-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libstdc++-devel-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 2520018 checksum: sha256:5a2474c2e817b008212e5a94770d8bfbaad17e9ebba2a38d734907e594ac2aac name: libstdc++-devel evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libtool-ltdl-2.4.6-46.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 37581 checksum: sha256:85f38e641398438f7f08526d7003f47e47a14369798464fd67c465134258e964 name: libtool-ltdl evr: 2.4.6-46.el9 sourcerpm: libtool-2.4.6-46.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libubsan-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 178996 checksum: sha256:d0adfda8f1d8ddf05a46292228e31cf887d731e7999154603e148e3d70d1f182 name: libubsan evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libuv-1.42.0-2.el9_4.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 150129 checksum: sha256:4dc8a40da74e0f9823356460ee11f183c70f382953700fffef0c448198a677cc name: libuv evr: 1:1.42.0-2.el9_4 sourcerpm: libuv-1.42.0-2.el9_4.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libxcrypt-devel-4.4.18-3.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 33051 checksum: sha256:9d621f33df35b9c274b8d65457d6c67fc1522b6c62cf7b2341a4a99f39a93507 name: libxcrypt-devel evr: 4.4.18-3.el9 sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libzstd-devel-1.5.5-1.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 53186 checksum: sha256:4eb008a503fdb6437022c77acef155b2822c62622cb486551d3253c28dd00660 name: libzstd-devel evr: 1.5.5-1.el9 sourcerpm: zstd-1.5.5-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/llvm-21.1.8-2.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 24409660 checksum: sha256:cf84a4f681a81b75922b81f462a25c1eda2329c7512c844489691b37c8367816 name: llvm evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/llvm-devel-21.1.8-2.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 6394014 checksum: sha256:c64d836b57bada150c36de67a88aea4747685f21b8b23bd4d0d0922b56381d29 name: llvm-devel evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/llvm-filesystem-21.1.8-2.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 20175 checksum: sha256:87b55002280f29f59e8452cf184307ce0ffcf613a8967f726cb5b3438ecbc70a name: llvm-filesystem evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/llvm-googletest-21.1.8-2.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 474811 checksum: sha256:62528321de99e8ccf8b82deb55a6837fac3ae6575a5d0b4ab57c79c8a7b92906 name: llvm-googletest evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/llvm-libs-21.1.8-2.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 31011258 checksum: sha256:47a47b60c922d628f7e0fa6c7e58484cd416221d6fbcd2b4708f5c901d6aecb4 name: llvm-libs evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/llvm-static-21.1.8-2.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 46022845 checksum: sha256:27e1bd90729ad393e1abf9a3dd66c5500b06d55b611d87e9c6005f9a77c90f0d name: llvm-static evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/llvm-test-21.1.8-2.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 759720 checksum: sha256:37e49775e95417bf1be7e4e9bdd5034c03457a218a6c3c67853c613fa6376796 name: llvm-test evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/n/ncurses-c++-libs-6.2-12.20210508.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 36630 checksum: sha256:925b0aded13b838171c10781d54e71060de5f4db26eff1c05525ec7a89c6f54e name: ncurses-c++-libs evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/n/ncurses-devel-6.2-12.20210508.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 745479 checksum: sha256:712408157ed0145b0b179b3abf69e458a28f0095c3906179fea1a0b396feb832 name: ncurses-devel evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/o/openssl-devel-3.5.8-1.el9_8.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 5039228 checksum: sha256:49aec716fb44d8a07528f59a635d40a47a847b05e6cf57735994e9c2430efa12 name: openssl-devel evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/p/policycoreutils-python-utils-3.6-5.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 84137 checksum: sha256:fae42a122e2848cfe99736012348f9c008c4d35e818e72e590e993e58d9858ad name: policycoreutils-python-utils evr: 3.6-5.el9 sourcerpm: policycoreutils-3.6-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/p/python3-audit-3.1.5-8.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 91000 checksum: sha256:4bc818f51fdd6846442e1e961b83e2ba777736e1fd28519e4ffdcc8e73c49154 name: python3-audit evr: 3.1.5-8.el9 sourcerpm: audit-3.1.5-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/p/python3-distro-1.5.0-7.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 41452 checksum: sha256:5cf4276217a72649895226707d4c0e3edd6ea64b66702793fab3907177c73069 name: python3-distro evr: 1.5.0-7.el9 sourcerpm: python-distro-1.5.0-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/p/python3-libselinux-3.6-3.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 191826 checksum: sha256:dcc609ec4df45a30606ce85999c9cf45e819d4f8e8479662dfaf6bfb1b27dd2d name: python3-libselinux evr: 3.6-3.el9 sourcerpm: libselinux-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/p/python3-libsemanage-3.6-5.el9_6.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 82350 checksum: sha256:1a32b9c090a98ddffe5ef70e4485dae8c295d942d87e15753e1cb5f01e23bc97 name: python3-libsemanage evr: 3.6-5.el9_6 sourcerpm: libsemanage-3.6-5.el9_6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/p/python3-policycoreutils-3.6-5.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 2219410 checksum: sha256:eb5d83e26cee47b23b4b802d98cae521f41d8d0d8451a9e5029cd9b5016dd00b name: python3-policycoreutils evr: 3.6-5.el9 sourcerpm: policycoreutils-3.6-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/s/scl-utils-2.0.3-4.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 42050 checksum: sha256:b7c093e050e3d127b98bd1002e46dee9cfbbc6279978aee17d7fad30b42a41ec name: scl-utils evr: 1:2.0.3-4.el9 sourcerpm: scl-utils-2.0.3-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/a/alternatives-1.24-2.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 42137 checksum: sha256:6f7c0667ac015bc0d40836c9f55c73ebf65a209069f69aa8f58e6b4655c820a8 name: alternatives evr: 1.24-2.el9 sourcerpm: chkconfig-1.24-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/a/audit-libs-3.1.5-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 128901 checksum: sha256:11880ec70b575841843cbee0853e03e50a0506321ea0a6f76c0c8145d79ae531 name: audit-libs evr: 3.1.5-8.el9 sourcerpm: audit-3.1.5-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/basesystem-11-13.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 8229 checksum: sha256:f498b0813fa1a825d550e8e3a9e42255eabfa18e6fc96adfc6cc8fa7e16dd513 name: basesystem evr: 11-13.el9 sourcerpm: basesystem-11-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/bash-5.1.8-9.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1760045 checksum: sha256:7dc1febec9c2fb184ed4407f8a188ab267b7e46b3534866f702c6266008ababa name: bash evr: 5.1.8-9.el9 sourcerpm: bash-5.1.8-9.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/binutils-2.35.2-72.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 5021411 checksum: sha256:72e9fd9c4976413060df02e37d358fca208ab144d78e321f448a488d50967155 name: binutils evr: 2.35.2-72.el9 sourcerpm: binutils-2.35.2-72.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/binutils-gold-2.35.2-72.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 908723 checksum: sha256:269bb24ded2f23dcdb74c04597b13281ce6ac47a87a14831ee639d985323bd04 name: binutils-gold evr: 2.35.2-72.el9 sourcerpm: binutils-2.35.2-72.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/bzip2-libs-1.0.8-11.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 47655 checksum: sha256:8267a866b9289ac4e4a92cb4642adcdeff97c2ed816ddda87ed5d5e9d9431a2f name: bzip2-libs evr: 1.0.8-11.el9 sourcerpm: bzip2-1.0.8-11.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/ca-certificates-2025.2.80_v9.0.305-91.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1072208 checksum: sha256:0554bf65d573950e7d550a07bf7d0b3def85ca3b45a7fbde4cce7cadd9a476a7 name: ca-certificates evr: 2025.2.80_v9.0.305-91.el9 sourcerpm: ca-certificates-2025.2.80_v9.0.305-91.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/coreutils-8.32-41.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1175876 checksum: sha256:22827aedd764c1ab706086965859e7f8fef0d719ac0b3d8735f6b8e53b0a13e9 name: coreutils evr: 8.32-41.el9_8.1 sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 2115646 checksum: sha256:b449955249a6d2da7369522a5ac2759919d36633e129ba88924057814906d5f5 name: coreutils-common evr: 8.32-41.el9_8.1 sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/cracklib-2.9.6-28.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 102026 checksum: sha256:d85216b672a15e5dd8cc771b853e3b73e832034949ee23998d8403609fba00a2 name: cracklib evr: 2.9.6-28.el9 sourcerpm: cracklib-2.9.6-28.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/cracklib-dicts-2.9.6-28.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 3829400 checksum: sha256:807345f95c448cb58d4db8d059f76e4c139ef029887d59b431efd20db934745a name: cracklib-dicts evr: 2.9.6-28.el9 sourcerpm: cracklib-2.9.6-28.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/crypto-policies-20260224-1.gitea0f072.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 98707 checksum: sha256:a9ad86b8df75a7c9c29c6f6dd4b8f78cfa1f42b492eff0d11b82c457d37a7f9f name: crypto-policies evr: 20260224-1.gitea0f072.el9_8 sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/crypto-policies-scripts-20260224-1.gitea0f072.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 111065 checksum: sha256:cec44db94e9132008a7fe4e1aa764a29e9c125989c9ac2411f3cf6f8fb669dfd name: crypto-policies-scripts evr: 20260224-1.gitea0f072.el9_8 sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/curl-7.76.1-40.el9_8.7.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 303973 checksum: sha256:bd967ad3119215b0462c7a850b62f69a2fb7ef16317d72bec1e52ce585bb1260 name: curl evr: 7.76.1-40.el9_8.7 sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/cyrus-sasl-lib-2.1.27-22.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 771760 checksum: sha256:7e4f331fc477f0a8482c825ab1b6bfec7f4007481f4eb53fde7fa0ef2d1f6cde name: cyrus-sasl-lib evr: 2.1.27-22.el9 sourcerpm: cyrus-sasl-2.1.27-22.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/d/diffutils-3.7-12.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 405687 checksum: sha256:524412f7ce56095508190116cb8ae141737857e4447979330c3cf75ca7017e6b name: diffutils evr: 3.7-12.el9 sourcerpm: diffutils-3.7-12.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-debuginfod-client-0.194-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 42824 checksum: sha256:bbcf05d0b46d42c85807d774788edc39528a6898bd880baf11fb77729f59272d name: elfutils-debuginfod-client evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-default-yama-scope-0.194-1.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 8949 checksum: sha256:c473f42c0fec97e2830580110509e4522bc62d9b11761d062499354a0bf58959 name: elfutils-default-yama-scope evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-libelf-0.194-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 203006 checksum: sha256:04ff63b7b669b16827f3688baa0be4a2782f1405db3c6d3ee03c0e4cebc2cdf2 name: elfutils-libelf evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-libs-0.194-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 271645 checksum: sha256:b90a6ad3e465995538785a2536986ad705625daf606d6258bf23d1dd29aec208 name: elfutils-libs evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/environment-modules-5.3.0-2.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 605165 checksum: sha256:569419a5a9256714a4d0f665ff2bada6f565bcc104479115c584cdbe897cf6bf name: environment-modules evr: 5.3.0-2.el9 sourcerpm: environment-modules-5.3.0-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.5.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 122957 checksum: sha256:d13bd77f429835b303d388e24811fb935060be4788c8fe4cf87703640f2337e0 name: expat evr: 2.5.0-6.el9_8.5 sourcerpm: expat-2.5.0-6.el9_8.5.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/f/filesystem-3.16-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 5003914 checksum: sha256:484bc41109c49066cf350344150abe144e63263e0fafa0bf12c5a47f853e6a49 name: filesystem evr: 3.16-5.el9 sourcerpm: filesystem-3.16-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/f/findutils-4.8.0-7.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 564807 checksum: sha256:158af4d5ecbd8b87f0da762ea1655bd4c86512071a95d8307eda3e0b3991105d name: findutils evr: 1:4.8.0-7.el9 sourcerpm: findutils-4.8.0-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gawk-5.1.0-6.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1026105 checksum: sha256:54365d2a6150079c2dc5003eeb94ada32fc15801b5db05422361f02ed58e6772 name: gawk evr: 5.1.0-6.el9_8.1 sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 60311 checksum: sha256:74fffe15dd7f5a41c7d1990c2804defa1b45fb845da29465b73a81d5866e8a72 name: gdbm-libs evr: 1:1.23-1.el9 sourcerpm: gdbm-1.23-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-2.34-275.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1813928 checksum: sha256:e40a78100f731b5f5a1b235880a0aaad5b08bb32e6521e90535b7f4a94c6e9e9 name: glibc evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-common-2.34-275.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 312665 checksum: sha256:24e1c7189d101531c526dd3cc1a42ab62d89f874824b54fb6b518ec24f190554 name: glibc-common evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-minimal-langpack-2.34-275.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 30881 checksum: sha256:b1348c4c4fe3da979f342b0c27ff0d33a11688274a0b6708292d7750bbc8d853 name: glibc-minimal-langpack evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gmp-6.2.0-13.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 275679 checksum: sha256:df01d909e4613514b1844d6ca26d0bcdff8a659762e507188d04ed046fb0cec4 name: gmp evr: 1:6.2.0-13.el9 sourcerpm: gmp-6.2.0-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/grep-3.6-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 276244 checksum: sha256:583a247a199901d44dc8a96d46010e15f6211f98f7c61ba089825155b0562520 name: grep evr: 3.6-5.el9 sourcerpm: grep-3.6-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/groff-base-1.22.4-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1088949 checksum: sha256:452cfe5372c834bb174ef1f6eed4d0aa6179420fd572163467ac9036fc7a3a1d name: groff-base evr: 1.22.4-10.el9 sourcerpm: groff-1.22.4-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gzip-1.12-2.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 171305 checksum: sha256:efafc848fdaa3a8e5e77baddc07abc7d800dc973efd44ecf492bc64dca4fabe6 name: gzip evr: 1.12-2.el9_8 sourcerpm: gzip-1.12-2.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/i/info-6.7-15.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 230301 checksum: sha256:c5ae65876c73c6f4e240081431745f5ba0a91d10a4bfb8a5d162ca3d6f039202 name: info evr: 6.7-15.el9 sourcerpm: texinfo-6.7-15.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/j/jansson-2.14-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 50251 checksum: sha256:7c002eb9c81bad49b8a11d9790af6220cce5fb882be7ca11335882d079d1473f name: jansson evr: 2.14-1.el9 sourcerpm: jansson-2.14-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/j/json-c-0.14-11.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 45052 checksum: sha256:fff625bf4f0753eb7323b8933d264f3cc5c992bfecba8b082b204849297149cc name: json-c evr: 0.14-11.el9 sourcerpm: json-c-0.14-11.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/k/keyutils-libs-1.6.3-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 34341 checksum: sha256:d747ed6e1916d8ea400c89ad6078a8c298e30d652ec21985c93539e34c587a73 name: keyutils-libs evr: 1.6.3-1.el9 sourcerpm: keyutils-1.6.3-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/k/krb5-libs-1.21.1-10.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 793489 checksum: sha256:f8bbc9abe0da1ebe5bc028154bd54d465fe0682ddbb64c45882b84ff09f40e1d name: krb5-libs evr: 1.21.1-10.el9_8 sourcerpm: krb5-1.21.1-10.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/less-590-6.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 165028 checksum: sha256:fa762484ba40e0b7eb1c25531a66a0b578b6141cabb6f73d865c13ccdf75c1c9 name: less evr: 590-6.el9 sourcerpm: less-590-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libacl-2.4.0-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 31468 checksum: sha256:70ba010505e9805254f772c3dd9cd9e6176fc9e007e8c9be7204c44f85d8bbd2 name: libacl evr: 2.4.0-1.el9_8 sourcerpm: acl-2.4.0-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libarchive-3.5.3-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 400797 checksum: sha256:7fd7a2981416def9d96892ea11aec3b9af146a95cae11af59a2fdee22fb52516 name: libarchive evr: 3.5.3-11.el9_8 sourcerpm: libarchive-3.5.3-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libatomic-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 26108 checksum: sha256:bebe2e8fd98c64d1667e3de1eb3751b7b641bf5d0cd870ed6445fea5c4526326 name: libatomic evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libattr-2.6.0-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 23276 checksum: sha256:ec08036348dbe2ee41645bdb96eb485b9db5121ec0524258b0639426a22a49bc name: libattr evr: 2.6.0-1.el9_8 sourcerpm: attr-2.6.0-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libblkid-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 113931 checksum: sha256:2c38ac06d3a267b62fc5ea4e149a25c4287c19157f4f18e0f7edea4787b27e15 name: libblkid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libbrotli-1.0.9-9.el9_7.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 325179 checksum: sha256:f5237abc90191238333c1214da97b5202c8a15c2be3ab401ee10d95343cfdf17 name: libbrotli evr: 1.0.9-9.el9_7 sourcerpm: brotli-1.0.9-9.el9_7.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcap-2.48-10.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 78021 checksum: sha256:1ac3014c33b84d7a492b99d46d47940b096e034a3d5886e16ace7159724be012 name: libcap evr: 2.48-10.el9_8.1 sourcerpm: libcap-2.48-10.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcap-ng-0.8.2-7.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 36033 checksum: sha256:dc4eae31749196c0043225c6749e7306ff71f081c09cbdb2fc98a561087c4474 name: libcap-ng evr: 0.8.2-7.el9 sourcerpm: libcap-ng-0.8.2-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcbor-0.7.0-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 59368 checksum: sha256:93a2f44044ab11225b1123bc9df4f4d09c0a5f3251818e7d144ca64fd12c0957 name: libcbor evr: 0.7.0-5.el9 sourcerpm: libcbor-0.7.0-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcom_err-1.46.5-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 26607 checksum: sha256:b7e5c8fe9d9f15864966f46c124659de6cb9137d01e213b3e8cac00d10aab55a name: libcom_err evr: 1.46.5-8.el9 sourcerpm: e2fsprogs-1.46.5-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.7.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 292483 checksum: sha256:408a3da221a1d3856a4b9ab7a7c70901430f71521ef9d05f9b847c0deb27b160 name: libcurl evr: 7.76.1-40.el9_8.7 sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libdb-5.3.28-57.el9_6.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 727417 checksum: sha256:3a912b2a0a6226695a5773138ce5ce090c9fb155151dffe732b8d52e6dd22d63 name: libdb evr: 5.3.28-57.el9_6 sourcerpm: libdb-5.3.28-57.el9_6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libeconf-0.4.1-7.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 32324 checksum: sha256:8a5e117c2690c82835c6013f1fbac37b026f3e953fbffbd84c2f5ef6cf8df571 name: libeconf evr: 0.4.1-7.el9_8 sourcerpm: libeconf-0.4.1-7.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libedit-3.1-39.20210216cvs.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 110092 checksum: sha256:93964f8c06574404f4a5b44781b698f556fbc22f7ae3c82d4d540619772f6816 name: libedit evr: 3.1-39.20210216cvs.el9 sourcerpm: libedit-3.1-39.20210216cvs.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libevent-2.1.13-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 268476 checksum: sha256:27a85302d64c15bfd383929eedcdeabef9bfc8f0a1e085b0501d0de73cb2bf74 name: libevent evr: 2.1.13-1.el9_8 sourcerpm: libevent-2.1.13-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libfdisk-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 156711 checksum: sha256:af043918fc50ce5b3de50c48c3de0143140b692fbf639db6f259270c4211a776 name: libfdisk evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libffi-3.4.2-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 38554 checksum: sha256:d33e180b97a603542cb6f1a78b1c3b0ce4af1bc59ee0bb32620c98a629726bc4 name: libffi evr: 3.4.2-8.el9 sourcerpm: libffi-3.4.2-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libfido2-1.13.0-2.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 100573 checksum: sha256:e56e963635b92f407471c7c5698d602135b135bda4515ecc75ac52dd1d38c7e4 name: libfido2 evr: 1.13.0-2.el9 sourcerpm: libfido2-1.13.0-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgcc-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 81211 checksum: sha256:6923218fdef581189a4b51c7ba158083597f1c6df08cae021a1d90e9d61938a9 name: libgcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgcrypt-1.10.0-13.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 469908 checksum: sha256:4a270fae0cf2f5ad846ce530664bbde72e798bb4a8196afb8fd2db93086c31c9 name: libgcrypt evr: 1.10.0-13.el9_8 sourcerpm: libgcrypt-1.10.0-13.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgfortran-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 435007 checksum: sha256:dffccd2856eae33a06d19180c60cf3d6944e9e6e08712b54cf83c49d77b21903 name: libgfortran evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgomp-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 262138 checksum: sha256:c8b3788fee442304e4158c802ff413df27d394b82f19c0f34ff43b5d3760470c name: libgomp evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgpg-error-1.42-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 222476 checksum: sha256:aee968114aed0238eb26cff42ec9b0819ad32e2bac99aa8124d55b480806aca5 name: libgpg-error evr: 1.42-5.el9 sourcerpm: libgpg-error-1.42-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libidn2-2.3.0-7.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 107549 checksum: sha256:657925cd0fc0abc03cc83ff3688e131a452ea673a5dcb815cd0fc168bf962fc7 name: libidn2 evr: 2.3.0-7.el9 sourcerpm: libidn2-2.3.0-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libmount-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 140081 checksum: sha256:152aee3abc8d97a37ff4ce2f5027d7e16e93ff2c77d25e900258da54e6fcc64e name: libmount evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libnghttp2-1.43.0-6.el9_8.2.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 79650 checksum: sha256:eefb6d331e38314bce55c28e02d67d022c83e41b4d5de91f86d9d846b381ac48 name: libnghttp2 evr: 1.43.0-6.el9_8.2 sourcerpm: nghttp2-1.43.0-6.el9_8.2.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libpipeline-1.5.3-4.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 52161 checksum: sha256:32d8aea6849a815e201cdce925fb3c6de2088cfcb7f6621e93495b605abe2f13 name: libpipeline evr: 1.5.3-4.el9 sourcerpm: libpipeline-1.5.3-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libpkgconf-1.7.3-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 38310 checksum: sha256:9bdfccf6b092e0683aa6984f7c6caa737b30c0b1495e16abb03b5d1a5f8e787a name: libpkgconf evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libpsl-0.21.1-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 67300 checksum: sha256:08968334789ba764986d3beb4745de28eb1e2ed401a03dba9d80e75e3179aa76 name: libpsl evr: 0.21.1-5.el9 sourcerpm: libpsl-0.21.1-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libpwquality-1.4.4-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 125712 checksum: sha256:1657d94bbd79f93dc7a79d474316813bde681ce3a7f62f73314ec4d630e39349 name: libpwquality evr: 1.4.4-8.el9 sourcerpm: libpwquality-1.4.4-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libselinux-3.6-3.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 89531 checksum: sha256:3d7249adbf19206e319cd24acc2e01b0da39975aa3e5af73bdb6c6d438108fac name: libselinux evr: 3.6-3.el9 sourcerpm: libselinux-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libselinux-utils-3.6-3.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 197588 checksum: sha256:63fc5e8f22ddff6ae1428b6802f7cfc4ef75c50199de107abecbff3937ad0c35 name: libselinux-utils evr: 3.6-3.el9 sourcerpm: libselinux-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsemanage-3.6-5.el9_6.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 120963 checksum: sha256:233d8270827b9166ad11827599800d2a09284d29e73af09c7a12bae251a9463c name: libsemanage evr: 3.6-5.el9_6 sourcerpm: libsemanage-3.6-5.el9_6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsepol-3.6-3.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 326966 checksum: sha256:496ed9e2d7fac9704afe764eab4c2c43b4a47e8c229c14498dd19786f98f80c0 name: libsepol evr: 3.6-3.el9 sourcerpm: libsepol-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsigsegv-2.13-4.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 30566 checksum: sha256:0998ac158161c9d5f3b97c5dc6e35becd84da0ddc5d347a8af581ada529b3b5c name: libsigsegv evr: 2.13-4.el9 sourcerpm: libsigsegv-2.13-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsmartcols-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 67440 checksum: sha256:1704e73a566c920796d877c7bc3e5a96ea0c0c4194109c7b085c1128c01856af name: libsmartcols evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libssh-0.10.4-19.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 223114 checksum: sha256:80f3962d3eb780ca6d6d4f8c6841b003a907d758ad8ad1c3d785e9cf327672f6 name: libssh evr: 0.10.4-19.el9_8 sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libssh-config-0.10.4-19.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 14764 checksum: sha256:ce06b99793b9e5abc50a58ebc3ef11037f2efbdb5f819a25f8de4994613194d4 name: libssh-config evr: 0.10.4-19.el9_8 sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libstdc++-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 723913 checksum: sha256:ae00c5009a2ee4682ec732cde66d3f4fcfc1a7099ba7b3701767d1748a4f2683 name: libstdc++ evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libtasn1-4.16.0-10.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 80446 checksum: sha256:322524934c9b1f0714d2299705dbd41ec93451078e8144babc88c51bd19f6e07 name: libtasn1 evr: 4.16.0-10.el9_8 sourcerpm: libtasn1-4.16.0-10.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libunistring-0.9.10-15.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 503151 checksum: sha256:f68934935fc209e7c595c5619df75f822cc832803e3ea6de2c92e3b91b4d5008 name: libunistring evr: 0.9.10-15.el9 sourcerpm: libunistring-0.9.10-15.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libutempter-1.2.1-6.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 30505 checksum: sha256:d352371cbb7d5bd0c53fc699df953c8c1f184b056690b3c4571e57a6634015c5 name: libutempter evr: 1.2.1-6.el9 sourcerpm: libutempter-1.2.1-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libuuid-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 32555 checksum: sha256:4d7bb4144053a30067a82423fb6e88fd08c7a69bd256eb21b08c0e3f4dbbaee6 name: libuuid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libverto-0.3.2-3.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 24651 checksum: sha256:80d6e32c111ab9c0b2c607475b6a6691cdf6abaec19fde27043e8710a94a8f0c name: libverto evr: 0.3.2-3.el9 sourcerpm: libverto-0.3.2-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libxcrypt-4.4.18-3.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 127655 checksum: sha256:f05030123425a5033bcca3f260313cafc199bc7bca57e9fb13c335bd087c35a7 name: libxcrypt evr: 4.4.18-3.el9 sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libxml2-2.9.13-14.el9_8.5.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 754850 checksum: sha256:39e0ffab5e42aa3688a39a9f27cecb77ee8dbf03682ca5b38c3e15ebc5493863 name: libxml2 evr: 2.9.13-14.el9_8.5 sourcerpm: libxml2-2.9.13-14.el9_8.5.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libzstd-1.5.5-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 283159 checksum: sha256:1229ed44dc7a68278682d7697c41d0abd7daedd242d90c6dc58a9aa6e76f9e6f name: libzstd evr: 1.5.5-1.el9 sourcerpm: zstd-1.5.5-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/lua-libs-5.4.4-4.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 132531 checksum: sha256:3bc4dceda442b11c804971ba71ffc2ef398371cd993b207b886de87fb9d7f596 name: lua-libs evr: 5.4.4-4.el9 sourcerpm: lua-5.4.4-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/lz4-libs-1.9.3-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 70696 checksum: sha256:e1dbd2c38a65b135427c7c8fe988ea70dc95f7e26c4c8177b7dcb23925020015 name: lz4-libs evr: 1.9.3-5.el9 sourcerpm: lz4-1.9.3-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/m/make-4.3-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 550249 checksum: sha256:351a22b0e6744bd329b1b0f22d9c3b69a6da970b575e6c76190cc84b0fe77450 name: make evr: 1:4.3-8.el9 sourcerpm: make-4.3-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/m/man-db-2.9.3-9.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1233088 checksum: sha256:44d2f613bd962c7ddd7202780d648dbdb1c9030965c36e57e72ad7b2e56fd6e2 name: man-db evr: 2.9.3-9.el9 sourcerpm: man-db-2.9.3-9.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/m/mpfr-4.1.0-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 249973 checksum: sha256:c238f7451d1fcc5431bef2904ba56a0bea51d28337ccb692f6d6a09286043a65 name: mpfr evr: 4.1.0-10.el9 sourcerpm: mpfr-4.1.0-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/n/ncurses-base-6.2-12.20210508.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 97840 checksum: sha256:d62dfd41f9688efa2cf1ceedb96084c63e297fbdcfd1e72bc6757c730092b60c name: ncurses-base evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/n/ncurses-libs-6.2-12.20210508.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 324624 checksum: sha256:b5dd452392d2f97bb050c9f5e5376998652c567dcbd8f035d26659b1b551b5c9 name: ncurses-libs evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openldap-2.6.8-4.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 291500 checksum: sha256:fd684316480b2f9a9448d550c2509e37016710ca0724ff3d17d91fa0be2bdc4e name: openldap evr: 2.6.8-4.el9 sourcerpm: openldap-2.6.8-4.el9.src.rpm - - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssh-9.9p1-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms - size: 432812 - checksum: sha256:d8bcf6348f5ee9d840e7f74eaaa53801cb9c48c1184c7254dd06aa73f597c690 + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssh-9.9p1-12.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 432970 + checksum: sha256:e2cc40546db9b067d9d969187aa1fea68ce399ee5f53e44ed91df6f2fc23f49c name: openssh - evr: 9.9p1-11.el9_8 - sourcerpm: openssh-9.9p1-11.el9_8.src.rpm - - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssh-clients-9.9p1-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms - size: 770368 - checksum: sha256:b5f49e9e5d66075859596aa71f62bc8cc951d50129dd43543b6aacd22386b1c1 + evr: 9.9p1-12.el9_8 + sourcerpm: openssh-9.9p1-12.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssh-clients-9.9p1-12.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 770215 + checksum: sha256:8601b26e577d6f8f0726061d96d941a4d8c2d5f0700a2bea5a957bbc17da6c22 name: openssh-clients - evr: 9.9p1-11.el9_8 - sourcerpm: openssh-9.9p1-11.el9_8.src.rpm + evr: 9.9p1-12.el9_8 + sourcerpm: openssh-9.9p1-12.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-3.5.8-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1546056 checksum: sha256:1ef4001b9a9df4695c80e986d3c8ceb7900bb6925f86fde09d16aff9f8d733f5 name: openssl evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 14220 checksum: sha256:158193d2f965db318148ec76e9347b530ac1f5379d849012c0a04d3c50cda478 name: openssl-fips-provider evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-fips-provider-so-3.0.7-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 529340 checksum: sha256:22374a51f8a529dcfcf3b3ebbb2095103e0e811a28953535e5a62e26d1233301 name: openssl-fips-provider-so evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-libs-3.5.8-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 2294194 checksum: sha256:23134af6ea097b94d8261367db01f91604ef3b508654caf4f7399e5c142abcc8 name: openssl-libs evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/p11-kit-0.26.4-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 589112 checksum: sha256:b9391ea6618098782c9325ccda3c9ca8bc0f3b035bd4f113a793cea18026c75e name: p11-kit evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/p11-kit-trust-0.26.4-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 162392 checksum: sha256:a57761123cd5836faf3d40251d16124557ffb452443bfa14cf59ac16e6c99970 name: p11-kit-trust evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pam-1.5.1-28.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 642233 checksum: sha256:12271815e3a5d35dbc4a8e1e3b52442b0d00b833816ef3a923da427a3bca561a name: pam evr: 1.5.1-28.el9_8.1 sourcerpm: pam-1.5.1-28.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pcre-8.44-4.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 187289 checksum: sha256:099feef7e71b82cf0234e37d824fc81353d51dee55694e05181fa686ab50efae name: pcre evr: 8.44-4.el9 sourcerpm: pcre-8.44-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pcre2-10.40-6.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 224938 checksum: sha256:29285f81cef68f73b4f8ff81ee8fdf4ceaa007933302119ed1615e4aa1091613 name: pcre2 evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pcre2-syntax-10.40-6.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 147926 checksum: sha256:d386b5e9b3a4b077b2ba143882e605750855dd3354f13c55fa12ed26908cb442 name: pcre2-syntax evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pkgconf-1.7.3-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 45196 checksum: sha256:aa38a3951a690d721a815ea8f9b01995a85f35a8540d8075205821011d0385e6 name: pkgconf evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pkgconf-m4-1.7.3-10.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 16054 checksum: sha256:91bafd6e06099451f60288327b275cfcc651822f6145176a157c6b0fa5131e02 name: pkgconf-m4 evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pkgconf-pkg-config-1.7.3-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 12398 checksum: sha256:47f1f744f96a2f3d360bc129837738dcebb1ee5032effc4472a891eea1d6a907 name: pkgconf-pkg-config evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/policycoreutils-3.6-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 251069 checksum: sha256:1c94341d45d675a1d583f86e16ff91eacf7fd4d6a89a987ba2c4c4c1783d35ce name: policycoreutils evr: 3.6-5.el9 sourcerpm: policycoreutils-3.6-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/popt-1.18-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 70067 checksum: sha256:f49c6d53f428bb3b610633af7b1053a6c1dc522762a9c6cb35195e519227eb51 name: popt evr: 1.18-8.el9 sourcerpm: popt-1.18-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/procps-ng-3.3.17-14.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 363344 checksum: sha256:a8c7514beb4c3cafa6341f43bbe285319d863b2893a34d91159dc8e90f615dd2 name: procps-ng evr: 3.3.17-14.el9 sourcerpm: procps-ng-3.3.17-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/publicsuffix-list-dafsa-20210518-3.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 60882 checksum: sha256:e6ec3390a736b085f403168c512a6b2b6f8e12a8fd5a4459f1c7dbbff2b67c33 name: publicsuffix-list-dafsa evr: 20210518-3.el9 sourcerpm: publicsuffix-list-20210518-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-3.9.25-7.el9_8.3.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 33143 checksum: sha256:67e6d2eca7f6558030dd215a4d2d3ede810231faad0f317eb2bcbc7e9ac619ce name: python3 evr: 3.9.25-7.el9_8.3 sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-libs-3.9.25-7.el9_8.3.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 8470487 checksum: sha256:014bbdef3d3d00d09c34df9aaf377337e338e31c4106da6c98b844339fcd50d6 name: python3-libs evr: 3.9.25-7.el9_8.3 sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-pip-wheel-21.3.1-2.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1198443 checksum: sha256:918041963ad5c3b91276bf1ed3307280673ca8352e2e632bbb3847b33d2bc15a name: python3-pip-wheel evr: 21.3.1-2.el9_8 sourcerpm: python-pip-21.3.1-2.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-setools-4.4.4-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 613411 checksum: sha256:f8e347a37155ff1b805c7d7ed1ad98db8df8fdfcf3d04c13c819e796604c892f name: python3-setools evr: 4.4.4-1.el9 sourcerpm: setools-4.4.4-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-setuptools-53.0.0-15.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 958725 checksum: sha256:e3c5b5927ad0c0bde27a95c54f7a1295965b317d225ece2e98acd365aa45d09f name: python3-setuptools evr: 53.0.0-15.el9 sourcerpm: python-setuptools-53.0.0-15.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-setuptools-wheel-53.0.0-15.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 479203 checksum: sha256:36dacb345e21bc0308ef2508f0c93995520a15ef0b56aab3593186c8dc9c0c5a name: python3-setuptools-wheel evr: 53.0.0-15.el9 sourcerpm: python-setuptools-53.0.0-15.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/r/readline-8.1-4.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 219015 checksum: sha256:2ae424b368c6747124b51b205b9e11d74aeaff56b3de90e8cbd36012e0d17707 name: readline evr: 8.1-4.el9 sourcerpm: readline-8.1-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/r/redhat-release-9.8-1.0.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 61683 checksum: sha256:fa7f1d93927c7f8c6f6563a8d221af659074f026e4b12cd74d456b0db1878164 name: redhat-release evr: 9.8-1.0.el9 sourcerpm: redhat-release-9.8-1.0.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/r/rpm-4.16.1.3-40.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 546367 checksum: sha256:29c03d6824a78ea747a1bfd2c9832807a049f8276c9647981a4be40f08225d76 name: rpm evr: 4.16.1.3-40.el9 sourcerpm: rpm-4.16.1.3-40.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/r/rpm-libs-4.16.1.3-40.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 307940 checksum: sha256:a6d053bde52dbc9c150a610afabd0c9437c65e0a9519c836b2d2223ac47b9c68 name: rpm-libs evr: 4.16.1.3-40.el9 sourcerpm: rpm-4.16.1.3-40.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/sed-4.8-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 315893 checksum: sha256:b73d314a8ef322a690bb69c49cb0dbd9a5ff18d2ba6b2973e18d2c076a52b62a name: sed evr: 4.8-10.el9 sourcerpm: sed-4.8-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/setup-2.13.7-10.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 153791 checksum: sha256:0891d395ce067121c28932534237ad1ce231f2bfa987411ad62e73a12d11eb6a name: setup evr: 2.13.7-10.el9 sourcerpm: setup-2.13.7-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/shadow-utils-4.9-16.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1244527 checksum: sha256:ccc46a8ea5f30d071e075ad53b5d39d191cfca4614090435528f2d2f944f88a2 name: shadow-utils evr: 2:4.9-16.el9 sourcerpm: shadow-utils-4.9-16.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/sqlite-libs-3.34.1-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 660770 checksum: sha256:0fbb8043f9c02870c831da433f69b856a6674d02b60306d9cc01149ec89ed239 name: sqlite-libs evr: 3.34.1-11.el9_8 sourcerpm: sqlite-3.34.1-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/systemd-libs-252-67.el9_8.6.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 629233 checksum: sha256:55e58c413ee69f19a69ba25905f96343076e21abe54cdff51f0d80e48be06769 name: systemd-libs evr: 252-67.el9_8.6 sourcerpm: systemd-252-67.el9_8.6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/t/tar-1.34-13.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 909271 checksum: sha256:ee4fa57c4bb87613f6fbd4b785a9e6162d43f046d1bbdd5022771652b931e9d0 name: tar evr: 2:1.34-13.el9_8 sourcerpm: tar-1.34-13.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/t/tcl-8.6.10-7.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1137015 checksum: sha256:a7cf45af14e65509a7f4d33422499372f6fb869bb82695dd4e9004e5c2e4ac1a name: tcl evr: 1:8.6.10-7.el9 sourcerpm: tcl-8.6.10-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 933286 checksum: sha256:8d6196e02853c0900c3c2e2367665beffb62cb2d0ff5f465ea2d00848a9a35dc name: tzdata evr: 2026c-1.el9_8 sourcerpm: tzdata-2026c-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/u/util-linux-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 2390152 checksum: sha256:3681bbe37d46309673f366135787f5713f0ef575e3cd413cd221af2512e3634b name: util-linux evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/u/util-linux-core-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 472949 checksum: sha256:de7ad826dd42b383d93f6dc6b720a26d4cd4815d00c0f093c2e28037a8881424 name: util-linux-core evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/v/vim-filesystem-8.2.2637-26.el9_8.21.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 24156 checksum: sha256:4362190912229b1a670e20a7e46d1f20fb2d1d30e80ec22342bba1318784f742 name: vim-filesystem evr: 2:8.2.2637-26.el9_8.21 sourcerpm: vim-8.2.2637-26.el9_8.21.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/x/xz-5.2.5-8.el9_0.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 235798 checksum: sha256:26ac21be6c1e396c7bcbaa9d4786e3275e996d9d78c01f75bbbc6962e6c9bef7 name: xz evr: 5.2.5-8.el9_0 sourcerpm: xz-5.2.5-8.el9_0.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/x/xz-libs-5.2.5-8.el9_0.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 94569 checksum: sha256:06931afb372ed4a6893e51558beaa6b0eab7adda0af93456fd99a081a8b80779 name: xz-libs evr: 5.2.5-8.el9_0 sourcerpm: xz-5.2.5-8.el9_0.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/z/zlib-1.2.11-40.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 94454 checksum: sha256:2e7f193e67235130c10f5579c2d2ec92e22e4098b6d12fb2855d93b1540c60f7 name: zlib @@ -1374,1365 +1374,1365 @@ arches: - arch: x86_64 packages: - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/checkpolicy-3.6-1.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 365931 checksum: sha256:3d12bc7e21276434108c97561f75d1854283afb73d4fface3b836acee09f8d98 name: checkpolicy evr: 3.6-1.el9 sourcerpm: checkpolicy-3.6-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/clang-21.1.8-2.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 7531133 checksum: sha256:d1d0a8e2c3c568a0fd4551ce982f6db5de0e0675e55e4083563df7d66324129b name: clang evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/clang-devel-21.1.8-2.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 4508426 checksum: sha256:16e04e008dbc07e4dbb6bc34ef4bbf0dc6aae119103b35ac5bce86e2e4072395 name: clang-devel evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/clang-libs-21.1.8-2.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 32793031 checksum: sha256:0d674d34e4ade76a3e2e273b3b967979dd8678870693b2b3d5670b48354c6673 name: clang-libs evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/clang-resource-filesystem-21.1.8-2.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 25845 checksum: sha256:d5cd22de3ab8fd0ee362d1fc7d030efbfabd573e776164987b9e92b7e88d1926 name: clang-resource-filesystem evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/clang-tools-extra-21.1.8-2.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 19688837 checksum: sha256:bf8f41f5f6e19c5e613c2a08552a09e5d73130fdd6e8163791731217286ab715 name: clang-tools-extra evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cmake-3.31.8-3.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 13989883 checksum: sha256:e67ea7aef1edd470e4ec22982e97871655abcdc0990754d4e8f147d4e7de317a name: cmake evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cmake-data-3.31.8-3.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 2829291 checksum: sha256:1cdc2e88a996c575b750483c8f562674e4b50a6ab414c7bbe6f6b641c1db7bd9 name: cmake-data evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cmake-filesystem-3.31.8-3.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 19309 checksum: sha256:b5ea81385a9e4e6a1ae2bb1175cd774af82f9c570a37008cde873ead466ba5f7 name: cmake-filesystem evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cpp-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 11226193 checksum: sha256:3c0ee1cb8b72f3f5176f8945ab518eb733ccc9f950d317fb5d5ac327d0eb9c90 name: cpp evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/e/emacs-filesystem-27.2-18.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 9495 checksum: sha256:49d7b88a05a72c15b78191a987e6def04fda8e2e4ff75711f715d0c0ecadc60f name: emacs-filesystem evr: 1:27.2-18.el9 sourcerpm: emacs-27.2-18.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 33982584 checksum: sha256:2082784165bbb246b6e5ef5921ed823f0a9709cacdf5823aa60695fd6d4819a2 name: gcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-c++-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 13474286 checksum: sha256:b073d8965ad8eed7520a2a1c9b7c961232511ad9188dcc8c92356160a9d51e37 name: gcc-c++ evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-toolset-14-14.0-2.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 10548 checksum: sha256:fd91c081a55265fe32d33db984799fad71776a58b385d2fab5573c69b5b9bf90 name: gcc-toolset-14 evr: 14.0-2.el9 sourcerpm: gcc-toolset-14-14.0-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-toolset-14-annobin-docs-12.88-1.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 89636 checksum: sha256:80d5c6c300561524fb2f855fce1867546d4452ea5c72cfb5d53f2117153e9805 name: gcc-toolset-14-annobin-docs evr: 12.88-1.el9 sourcerpm: gcc-toolset-14-annobin-12.88-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-toolset-14-annobin-plugin-gcc-12.88-1.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 1001848 checksum: sha256:0e5d40903d0dbd89956f16c78ad40341dc8dd3da7fb55000cca7bb5f09333086 name: gcc-toolset-14-annobin-plugin-gcc evr: 12.88-1.el9 sourcerpm: gcc-toolset-14-annobin-12.88-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-toolset-14-binutils-2.41-6.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 6901423 checksum: sha256:5decfcda1ae0231cd9c13706c4c0d7d816d2e8c1bbe3a291721df29c5a81a832 name: gcc-toolset-14-binutils evr: 2.41-6.el9 sourcerpm: gcc-toolset-14-binutils-2.41-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-toolset-14-dwz-0.14-1.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 130028 checksum: sha256:401e7ffc147e0b640c54160ad884fa0ff8aaf2f853844efa4089449bee724593 name: gcc-toolset-14-dwz evr: 0.14-1.el9 sourcerpm: gcc-toolset-14-dwz-0.14-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-toolset-14-gcc-14.2.1-13.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 49276047 checksum: sha256:88394bf3285516aafeb46142137eef4cfcbaec6dea65069300fa83b1b9591212 name: gcc-toolset-14-gcc evr: 14.2.1-13.el9 sourcerpm: gcc-toolset-14-gcc-14.2.1-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-toolset-14-gcc-c++-14.2.1-13.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 15461951 checksum: sha256:58526a393176dfb8e09febeec740e83184cb516071789640544a016c38900dec name: gcc-toolset-14-gcc-c++ evr: 14.2.1-13.el9 sourcerpm: gcc-toolset-14-gcc-14.2.1-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-toolset-14-gcc-gfortran-14.2.1-13.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 16316399 checksum: sha256:cfe1f8d7b41dff526d0f4557d2a174a0ca3316f09b598af00e707318941ff3c6 name: gcc-toolset-14-gcc-gfortran evr: 14.2.1-13.el9 sourcerpm: gcc-toolset-14-gcc-14.2.1-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-toolset-14-libquadmath-devel-14.2.1-13.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 199281 checksum: sha256:b87c01a01aca2e966d43510928f6474a1cbb5d19cffab5932c36e1f184f428e2 name: gcc-toolset-14-libquadmath-devel evr: 14.2.1-13.el9 sourcerpm: gcc-toolset-14-gcc-14.2.1-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-toolset-14-libstdc++-devel-14.2.1-13.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 3828722 checksum: sha256:56127110cd31f460a4cc4b42477459ad546fba010a170b3c35ca6b0922595311 name: gcc-toolset-14-libstdc++-devel evr: 14.2.1-13.el9 sourcerpm: gcc-toolset-14-gcc-14.2.1-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-toolset-14-runtime-14.0-2.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 58568 checksum: sha256:fd93bde11241e6ed3c9dbfad9ce9a9683645b9242ab679a7c7daeb314fdc3367 name: gcc-toolset-14-runtime evr: 14.0-2.el9 sourcerpm: gcc-toolset-14-14.0-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-toolset-15-binutils-2.44-5.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 6198721 checksum: sha256:6d230ef471ed2995ea3cb5eed9780e7f17f4ba95988f41acff3756007978d89d name: gcc-toolset-15-binutils evr: 2.44-5.el9 sourcerpm: gcc-toolset-15-binutils-2.44-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-toolset-15-gcc-15.2.1-7.1.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 54282132 checksum: sha256:64fea64a38e095f75e929e0cf0e85a93bb978dc2afe0cd83f4af5415549fc259 name: gcc-toolset-15-gcc evr: 15.2.1-7.1.el9_8 sourcerpm: gcc-toolset-15-gcc-15.2.1-7.1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-toolset-15-gcc-c++-15.2.1-7.1.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 17108710 checksum: sha256:bc0a9ab7188a36a022047d1f83a3b80d5c6b29b8762250726391990a6cf47dba name: gcc-toolset-15-gcc-c++ evr: 15.2.1-7.1.el9_8 sourcerpm: gcc-toolset-15-gcc-15.2.1-7.1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-toolset-15-libstdc++-devel-15.2.1-7.1.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 3994173 checksum: sha256:ea2e288330c49cd34dde9c681957ee9deac567eb1c7d235f72abebee987d4569 name: gcc-toolset-15-libstdc++-devel evr: 15.2.1-7.1.el9_8 sourcerpm: gcc-toolset-15-gcc-15.2.1-7.1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-toolset-15-runtime-15.0-9.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 54379 checksum: sha256:61549a047ab9510897119b3d7191be6f1a2d4ddaa45163a08e82daa0e653c0bf name: gcc-toolset-15-runtime evr: 15.0-9.el9 sourcerpm: gcc-toolset-15-15.0-9.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/git-core-2.52.0-1.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 5293286 checksum: sha256:6264aa556d583604f34def3674f5830a70cfee0aac283719e4df295db38acb53 name: git-core evr: 2.52.0-1.el9 sourcerpm: git-2.52.0-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/glibc-devel-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 46499 checksum: sha256:a3b6ed698d21192fa7c421094a5d6648411fba4252db28c96d629778c86e6cd5 name: glibc-devel evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/glibc-headers-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 567171 checksum: sha256:2124192aba2e7931cdf00c2dcd4b70b71b313790c28dcf09b2fb09cc9831c86f name: glibc-headers evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 2965145 checksum: sha256:2473df2cf5b65c762af7941fe87324e98dc0e8b42d1e5745051a0405e200b7f5 name: kernel-headers evr: 5.14.0-687.53.1.el9_8 sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libedit-devel-3.1-39.20210216cvs.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 54318 checksum: sha256:2ffb25f591c2df48d9c2b189c925cc1d2930e998239a8aa21fefad852d22a6a9 name: libedit-devel evr: 3.1-39.20210216cvs.el9 sourcerpm: libedit-3.1-39.20210216cvs.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 66075 checksum: sha256:b97b4e98c3c6f41dcfc2ceb4ffa1aba7a338b7cfd9e6c4f63e3160dd3cc033d3 name: libmpc evr: 1.2.1-4.el9 sourcerpm: libmpc-1.2.1-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libstdc++-devel-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 2524816 checksum: sha256:2d031d05fe073adc74919b8372763ae322615d9df23f4a2c642050ab4b389ce5 name: libstdc++-devel evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libtool-ltdl-2.4.6-46.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 38043 checksum: sha256:44f7303229bdb4c2975f9829e3dd13dc7984e2cb53ef0f85baf894b39f605c38 name: libtool-ltdl evr: 2.4.6-46.el9 sourcerpm: libtool-2.4.6-46.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libuv-1.42.0-2.el9_4.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 154427 checksum: sha256:e1fab39251239ccaad2fb4dbe6c55ec1ae60f76d4ae81582b06e6a58e30879b2 name: libuv evr: 1:1.42.0-2.el9_4 sourcerpm: libuv-1.42.0-2.el9_4.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libxcrypt-devel-4.4.18-3.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 33101 checksum: sha256:c1d171391a7d2e043a6953efd3df3e01edc9b4c6cdb54517e1608d204a5fce18 name: libxcrypt-devel evr: 4.4.18-3.el9 sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libzstd-devel-1.5.5-1.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 53219 checksum: sha256:5eff37e589fce787982840ace9bd4fa9a869287b186727be87d829e525e16624 name: libzstd-devel evr: 1.5.5-1.el9 sourcerpm: zstd-1.5.5-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/llvm-21.1.8-2.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 25095576 checksum: sha256:f8c20cee6bf4384b2e7f878c4497471f93f2335e07a31fc692e479ed72301a12 name: llvm evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/llvm-devel-21.1.8-2.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 6395510 checksum: sha256:e921941858f2e6cea6665bbb12c3856d1f6fec2cad215538dcd1ad3a6652c64b name: llvm-devel evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/llvm-filesystem-21.1.8-2.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 20224 checksum: sha256:b54fc55927e26da6954bbf9a396735a2a29383b15fe9e3d70b9d24cd90b1c500 name: llvm-filesystem evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/llvm-googletest-21.1.8-2.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 472823 checksum: sha256:67371a6df47643a3881eaeaf5ac490639953b14dd1b21b66fb73dc677e0f17df name: llvm-googletest evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/llvm-libs-21.1.8-2.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 32586819 checksum: sha256:6c71c84a680f89a551b312eac90c9ae2899c3bdfaacdc809e39d7da968657d5a name: llvm-libs evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/llvm-static-21.1.8-2.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 47099677 checksum: sha256:017f7ceabd6fd81e284b3506b220785b44928c852de13d0d1da1a07d6778d391 name: llvm-static evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/llvm-test-21.1.8-2.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 774709 checksum: sha256:e9461f6b89564ba94cc6bee94e480d6aeffd4bca4d913fea2f2a6d4e01677f5d name: llvm-test evr: 21.1.8-2.el9 sourcerpm: llvm-21.1.8-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/n/ncurses-c++-libs-6.2-12.20210508.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 38116 checksum: sha256:e9538f14a6430ca3a637ff753a421cb5ba6fae5594ad5178ce90b81d7be6a230 name: ncurses-c++-libs evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/n/ncurses-devel-6.2-12.20210508.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 745592 checksum: sha256:c5edeb1aebfe38d19b91b493503efccbc8baebbef505023e114ee51547985a9d name: ncurses-devel evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/o/openssl-devel-3.5.8-1.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 5039851 checksum: sha256:8042a4b1134610ef06c27b7314a5fcd4ce887d1065d4b9e861bc3f647d7fb792 name: openssl-devel evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/p/policycoreutils-python-utils-3.6-5.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 84137 checksum: sha256:fae42a122e2848cfe99736012348f9c008c4d35e818e72e590e993e58d9858ad name: policycoreutils-python-utils evr: 3.6-5.el9 sourcerpm: policycoreutils-3.6-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/p/python3-audit-3.1.5-8.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 90891 checksum: sha256:493229df98414e566fd0e16e06058e7902329f0b188ced24c8d434d2e097ec82 name: python3-audit evr: 3.1.5-8.el9 sourcerpm: audit-3.1.5-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/p/python3-distro-1.5.0-7.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 41452 checksum: sha256:5cf4276217a72649895226707d4c0e3edd6ea64b66702793fab3907177c73069 name: python3-distro evr: 1.5.0-7.el9 sourcerpm: python-distro-1.5.0-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/p/python3-libselinux-3.6-3.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 196472 checksum: sha256:7af821a0ee7c7b56df79de25fe35cc2d0fd6f45df5c3bcec2c5e72d7378ba265 name: python3-libselinux evr: 3.6-3.el9 sourcerpm: libselinux-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/p/python3-libsemanage-3.6-5.el9_6.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 82730 checksum: sha256:8a17df19f0ff5dbb98fe608999cb2370983d8565658df01d0993b3028cbf28d6 name: python3-libsemanage evr: 3.6-5.el9_6 sourcerpm: libsemanage-3.6-5.el9_6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/p/python3-policycoreutils-3.6-5.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 2219410 checksum: sha256:eb5d83e26cee47b23b4b802d98cae521f41d8d0d8451a9e5029cd9b5016dd00b name: python3-policycoreutils evr: 3.6-5.el9 sourcerpm: policycoreutils-3.6-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/s/scl-utils-2.0.3-4.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 42223 checksum: sha256:164d245bec95c7dcbba881fd88ee567bc6d5859329c91ae05a6ad5429700bdbc name: scl-utils evr: 1:2.0.3-4.el9 sourcerpm: scl-utils-2.0.3-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/a/alternatives-1.24-2.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 42874 checksum: sha256:1c520b9bf7b592d936bb347a5107702e51678e160b88ecfbba6a30e35e47d24e name: alternatives evr: 1.24-2.el9 sourcerpm: chkconfig-1.24-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/a/audit-libs-3.1.5-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 130600 checksum: sha256:637ac2995ce1a6c222772b60f6bc6e6f2829355d2c88dfb1262fb76146d985ae name: audit-libs evr: 3.1.5-8.el9 sourcerpm: audit-3.1.5-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/basesystem-11-13.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 8229 checksum: sha256:f498b0813fa1a825d550e8e3a9e42255eabfa18e6fc96adfc6cc8fa7e16dd513 name: basesystem evr: 11-13.el9 sourcerpm: basesystem-11-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/bash-5.1.8-9.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1769540 checksum: sha256:d3adf8b09aa0bf935c67aa12444e0ee02f70a82c2682bfb2b02bda0a989bb806 name: bash evr: 5.1.8-9.el9 sourcerpm: bash-5.1.8-9.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/binutils-2.35.2-72.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 4821853 checksum: sha256:bda706d43bf47267e31db8ac62fe3206122f97ff035daa6c93ce9cd5063a63ca name: binutils evr: 2.35.2-72.el9 sourcerpm: binutils-2.35.2-72.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/binutils-gold-2.35.2-72.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 758393 checksum: sha256:4d429d1030d8e1c610ba5aea83f8c63090033f440b2c8f788f0e0acd4a3c51b3 name: binutils-gold evr: 2.35.2-72.el9 sourcerpm: binutils-2.35.2-72.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/bzip2-libs-1.0.8-11.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 46333 checksum: sha256:948f763ed17672b8dd83356541e27a53ce97c6df38339c4416a188d452ca4d1e name: bzip2-libs evr: 1.0.8-11.el9 sourcerpm: bzip2-1.0.8-11.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/ca-certificates-2025.2.80_v9.0.305-91.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1072208 checksum: sha256:0554bf65d573950e7d550a07bf7d0b3def85ca3b45a7fbde4cce7cadd9a476a7 name: ca-certificates evr: 2025.2.80_v9.0.305-91.el9 sourcerpm: ca-certificates-2025.2.80_v9.0.305-91.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/coreutils-8.32-41.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1222083 checksum: sha256:374257c4cd69107333a7f524dd99579f3ea3ab842b66168eade0a3475fb6eea1 name: coreutils evr: 8.32-41.el9_8.1 sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 2113503 checksum: sha256:41f69eb8b2087feaa98d0228fb933b6fe8af20a4bf371cfef51e11cbd1f84b4e name: coreutils-common evr: 8.32-41.el9_8.1 sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/cracklib-2.9.6-28.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 102444 checksum: sha256:3b415381d4bd307686268ec42f646c7770f6a815de73a40a88aab7a7061b30a9 name: cracklib evr: 2.9.6-28.el9 sourcerpm: cracklib-2.9.6-28.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/cracklib-dicts-2.9.6-28.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 3829431 checksum: sha256:61c11d3c23b62016b9939f917bb7f7e03cba324eb4ad35b375387ce9f18e25a1 name: cracklib-dicts evr: 2.9.6-28.el9 sourcerpm: cracklib-2.9.6-28.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/crypto-policies-20260224-1.gitea0f072.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 98707 checksum: sha256:a9ad86b8df75a7c9c29c6f6dd4b8f78cfa1f42b492eff0d11b82c457d37a7f9f name: crypto-policies evr: 20260224-1.gitea0f072.el9_8 sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/crypto-policies-scripts-20260224-1.gitea0f072.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 111065 checksum: sha256:cec44db94e9132008a7fe4e1aa764a29e9c125989c9ac2411f3cf6f8fb669dfd name: crypto-policies-scripts evr: 20260224-1.gitea0f072.el9_8 sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/curl-7.76.1-40.el9_8.7.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 307440 checksum: sha256:4445a80df491cd5cf31ef276194d6827bfb4015b7d7014ba968f73d49ddae9ca name: curl evr: 7.76.1-40.el9_8.7 sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/cyrus-sasl-lib-2.1.27-22.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 786202 checksum: sha256:a85ebdee7a9a49990f87e4709c368212e6a54ecf18c88a3dd54d823a82443898 name: cyrus-sasl-lib evr: 2.1.27-22.el9 sourcerpm: cyrus-sasl-2.1.27-22.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/d/diffutils-3.7-12.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 411559 checksum: sha256:2d4c4fdfc10215af3c957c24995b79a26e27e6d76de4ed1f5198d25bf7ef9671 name: diffutils evr: 3.7-12.el9 sourcerpm: diffutils-3.7-12.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-debuginfod-client-0.194-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 43826 checksum: sha256:1635fd1ecaa9492fa925956dfd56d10063ca336619218f124ab45df0a38b41b0 name: elfutils-debuginfod-client evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-default-yama-scope-0.194-1.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 8949 checksum: sha256:c473f42c0fec97e2830580110509e4522bc62d9b11761d062499354a0bf58959 name: elfutils-default-yama-scope evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-libelf-0.194-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 205864 checksum: sha256:00bbe4776149d8ccedcdf19dd6ceb08c3bb42ff41b98d744abe101af0b11a6c5 name: elfutils-libelf evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-libs-0.194-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 274670 checksum: sha256:ad4f6d425cbc975cead56a2c982e38b0146d00944cbd9b3072d3d1f1271237a5 name: elfutils-libs evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/environment-modules-5.3.0-2.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 604214 checksum: sha256:abee5229dce8c09f567626c0a6d741e8e3b30f8fd54d860365a25bec901cd680 name: environment-modules evr: 5.3.0-2.el9 sourcerpm: environment-modules-5.3.0-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.5.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 129088 checksum: sha256:e6e7edd632fdd1dc4ad94b19b7ab88646f3ef8c1c7956bac58985c3e7118a362 name: expat evr: 2.5.0-6.el9_8.5 sourcerpm: expat-2.5.0-6.el9_8.5.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/f/filesystem-3.16-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 5003807 checksum: sha256:9567592e6e32a9ebd45584cc4feb5d00812f143fcb2d8cd8b1d95108f4f66a2d name: filesystem evr: 3.16-5.el9 sourcerpm: filesystem-3.16-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/f/findutils-4.8.0-7.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 563531 checksum: sha256:a6328afea0a11647b7fb5c48436f0af6c795407bac0650676d3196dd47070de6 name: findutils evr: 1:4.8.0-7.el9 sourcerpm: findutils-4.8.0-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gawk-5.1.0-6.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1046649 checksum: sha256:fcc5e724c32597cf781626728f0faff2f0e5ed6455ab95af4883eefca30a074e name: gawk evr: 5.1.0-6.el9_8.1 sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 60152 checksum: sha256:c8b8346a98d921206666ce740a3647a52ad7a87c2d01d73166165b3e9a789a6c name: gdbm-libs evr: 1:1.23-1.el9 sourcerpm: gdbm-1.23-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 2083064 checksum: sha256:7d2d420b97c05c09ee1e9bd881cb0725fe8d89688b933f411f278cb23210c65d name: glibc evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-common-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 321585 checksum: sha256:f23581b888783f576bd3a55503618a48f74f468fcfd4837bbd7a2d00fe7f3530 name: glibc-common evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-minimal-langpack-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 30913 checksum: sha256:6cc48d78bf2ceacfa5b58633b648c564b66505f4677adea8eb663fe90acd76f2 name: glibc-minimal-langpack evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gmp-6.2.0-13.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 326840 checksum: sha256:d4529445e30b7eb9a8225b0539f70d26d585d7fe306296f948ea73114d1c171f name: gmp evr: 1:6.2.0-13.el9 sourcerpm: gmp-6.2.0-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/grep-3.6-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 279174 checksum: sha256:5556895ff1817066ca71b50785615e944b0fcc7e1c94c983087c7c691819623d name: grep evr: 3.6-5.el9 sourcerpm: grep-3.6-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/groff-base-1.22.4-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1133828 checksum: sha256:4d8ff13569b3b231b3fb847e9e22615c6e08215d1f2c0c78eac2e345b9efd394 name: groff-base evr: 1.22.4-10.el9 sourcerpm: groff-1.22.4-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gzip-1.12-2.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 172571 checksum: sha256:a87bdcce45011f232758c01bd99c564b5f6b549d391646cc573a132d22604b85 name: gzip evr: 1.12-2.el9_8 sourcerpm: gzip-1.12-2.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/i/info-6.7-15.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 233806 checksum: sha256:3643f98b45cc973073096608aaa45976d722fe284590ff7c1d5f93ad77ba0f8b name: info evr: 6.7-15.el9 sourcerpm: texinfo-6.7-15.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/j/jansson-2.14-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 49137 checksum: sha256:4e9aec51ee46d7265d6edd1245b5d5ab5e8336dc2a4ca17f2cace2ce8bae3761 name: jansson evr: 2.14-1.el9 sourcerpm: jansson-2.14-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/j/json-c-0.14-11.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 46136 checksum: sha256:b9bde4162250023103d95908fbca44fff6636a46176f92cf1761c1c3a4580a2f name: json-c evr: 0.14-11.el9 sourcerpm: json-c-0.14-11.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/k/keyutils-libs-1.6.3-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 34363 checksum: sha256:96d75824948387a884d206865db534cd3d46f32422efcb020c20060b59edb27c name: keyutils-libs evr: 1.6.3-1.el9 sourcerpm: keyutils-1.6.3-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/k/krb5-libs-1.21.1-10.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 790743 checksum: sha256:8906be9f2d414c5f4e1218db0c94955c2b3394b43a04b7dbcc2ef66c4340ebc6 name: krb5-libs evr: 1.21.1-10.el9_8 sourcerpm: krb5-1.21.1-10.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/less-590-6.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 166025 checksum: sha256:5bd040f9dd813167935fc390d546c119d90e0a9c77447a3d9ed1ef69c6f5a32a name: less evr: 590-6.el9 sourcerpm: less-590-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libacl-2.4.0-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 31657 checksum: sha256:a81fb7a4d7c946e9bd886ee3c471a4b5040dedbb8ffb2a388120b2094be93cc8 name: libacl evr: 2.4.0-1.el9_8 sourcerpm: acl-2.4.0-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libarchive-3.5.3-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 402853 checksum: sha256:3b8ed4523d8721a1fabc2c92e6871c353b8ff5fb555663ab006fe90a7be35a86 name: libarchive evr: 3.5.3-11.el9_8 sourcerpm: libarchive-3.5.3-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libattr-2.6.0-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 23752 checksum: sha256:9e37537f690c748f7f05faa80966072f118ec722e38ef332fe19cf22b087349f name: libattr evr: 2.6.0-1.el9_8 sourcerpm: attr-2.6.0-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libblkid-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 114192 checksum: sha256:a858400abe83a7955ae509c920f835a950ea2cf1156916823c595a23ba46d536 name: libblkid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libbrotli-1.0.9-9.el9_7.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 326278 checksum: sha256:81096e6aed022489306e2fe1d1496b2b689d8f0bf6c70a94b5bddb82356eeda1 name: libbrotli evr: 1.0.9-9.el9_7 sourcerpm: brotli-1.0.9-9.el9_7.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcap-2.48-10.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 78928 checksum: sha256:d4805439b10fa551b7535cf30ca28d4d5862132c9c429b2b31221bea7f43263a name: libcap evr: 2.48-10.el9_8.1 sourcerpm: libcap-2.48-10.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcap-ng-0.8.2-7.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 36752 checksum: sha256:ebddfc188d1ddbb0d6a238583cbc02dcb9fc0bd063a850b22d48980899976628 name: libcap-ng evr: 0.8.2-7.el9 sourcerpm: libcap-ng-0.8.2-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcbor-0.7.0-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 60575 checksum: sha256:588e8736af3376abfb3cdf372c10baef02c40d916a55958f3bee9767f9ad8526 name: libcbor evr: 0.7.0-5.el9 sourcerpm: libcbor-0.7.0-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcom_err-1.46.5-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 26980 checksum: sha256:b7593ee2d841c69573d8ed553b7416ef727b2c77c0473416a5dadf4b567bf547 name: libcom_err evr: 1.46.5-8.el9 sourcerpm: e2fsprogs-1.46.5-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.7.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 296930 checksum: sha256:044d7a379f0f0f6ed25b9d41439dedfb55b390108ae5a09d4acc6b3565c39ae1 name: libcurl evr: 7.76.1-40.el9_8.7 sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libdb-5.3.28-57.el9_6.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 755192 checksum: sha256:3246e76f197e2b60eb470b9b55d3e0dda2301b029f295fed9c38ff70b87c5b6b name: libdb evr: 5.3.28-57.el9_6 sourcerpm: libdb-5.3.28-57.el9_6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libeconf-0.4.1-7.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 33179 checksum: sha256:a570c5baaedeb1445bc2e4f3930b0a709411bbefbdbf463c3e006cbfc7018894 name: libeconf evr: 0.4.1-7.el9_8 sourcerpm: libeconf-0.4.1-7.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libedit-3.1-39.20210216cvs.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 112056 checksum: sha256:65a730688dfea27934b75af3acf30150c6d254c89d8b68b63233ae7f8b6c9b94 name: libedit evr: 3.1-39.20210216cvs.el9 sourcerpm: libedit-3.1-39.20210216cvs.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libevent-2.1.13-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 276162 checksum: sha256:c28b28573118d5cf0f1f68d96bc7c46d56671e45360698c894ca06ddf40163ae name: libevent evr: 2.1.13-1.el9_8 sourcerpm: libevent-2.1.13-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libfdisk-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 161769 checksum: sha256:1b861f267752e718ce696a80dcc06ef1dde8e9aa73fa2abed3775626d719c124 name: libfdisk evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libffi-3.4.2-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 40619 checksum: sha256:dde0012a94c6f3825e605b095b15767d89c2b87a5da097348310d7e87721c645 name: libffi evr: 3.4.2-8.el9 sourcerpm: libffi-3.4.2-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libfido2-1.13.0-2.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 102746 checksum: sha256:6da940c0528f3e4453db84cb85b402c8f4293a197b1921158df9651edb4845e0 name: libfido2 evr: 1.13.0-2.el9 sourcerpm: libfido2-1.13.0-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgcc-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 87280 checksum: sha256:77c66827ffc14df2f43612b26128b1fd58d5c9597d4d4e564aa239b161272872 name: libgcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgcrypt-1.10.0-13.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 523829 checksum: sha256:c07cd9f613809195b8691d28fd2f3bff55b2a83b9c1cdf4a32c681e0e32dfafb name: libgcrypt evr: 1.10.0-13.el9_8 sourcerpm: libgcrypt-1.10.0-13.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgfortran-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 813380 checksum: sha256:11d2f1c6c2ca35bdf7e866e80615d17d10601bc512905c8ef4f74ff8b0a3015a name: libgfortran evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgomp-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 263723 checksum: sha256:87b9a7316760374111290e6e4c76ee4d093566f08a7c7c91ad7827c5948afb69 name: libgomp evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgpg-error-1.42-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 225603 checksum: sha256:8248e20d7a253aa9c0dc7dc3d56b42e1def4fd5753ce8e8b9e980aa664fc9068 name: libgpg-error evr: 1.42-5.el9 sourcerpm: libgpg-error-1.42-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libidn2-2.3.0-7.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 107099 checksum: sha256:055f4ce6b721be7138dc2e45a6586412c65508acea3fe385a2655c129fe264f9 name: libidn2 evr: 2.3.0-7.el9 sourcerpm: libidn2-2.3.0-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libmount-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 142467 checksum: sha256:a9a2022eb9e39301bfcd3273573a108486b2b315c89247f147870016b2e9222c name: libmount evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libnghttp2-1.43.0-6.el9_8.2.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 80410 checksum: sha256:adb3260b6610917c07bda0a6bc521d5628bb1892a66d008f0fea81dc022ac699 name: libnghttp2 evr: 1.43.0-6.el9_8.2 sourcerpm: nghttp2-1.43.0-6.el9_8.2.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libpipeline-1.5.3-4.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 52912 checksum: sha256:c972030a8fbaa2d981f0e5fdfc42d6d5173dd047c94d86ab7732e3e53fc4e97a name: libpipeline evr: 1.5.3-4.el9 sourcerpm: libpipeline-1.5.3-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libpkgconf-1.7.3-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 38387 checksum: sha256:4feae5941b73640bd86b8d506a657cac5b770043db1464fbcd207721b2159dda name: libpkgconf evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libpsl-0.21.1-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 67454 checksum: sha256:ad1a62ef07682bb64a476c1a49f5cfc7abc9beb44775e7e511bf737e9a6bf99d name: libpsl evr: 0.21.1-5.el9 sourcerpm: libpsl-0.21.1-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libpwquality-1.4.4-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 126104 checksum: sha256:14b7ff2f7fdaf8ebec90261f4619ea7f7c3564c4de8483666de7ed4b1f49b66f name: libpwquality evr: 1.4.4-8.el9 sourcerpm: libpwquality-1.4.4-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libquadmath-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 188925 checksum: sha256:6fd5a850dc8e0a5b17c95089a8da0319beb8bf6ba68b633366d509458b332448 name: libquadmath evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libselinux-3.6-3.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 89722 checksum: sha256:ce1cc63a7212c39f5f2a35f719ee38d6418cf081ea78c9317f388d9f41e4a627 name: libselinux evr: 3.6-3.el9 sourcerpm: libselinux-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libselinux-utils-3.6-3.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 198410 checksum: sha256:e5d79885864cd5b2a307065b43ba1af1523ec7ac26eace2717c70ede1b6e4c56 name: libselinux-utils evr: 3.6-3.el9 sourcerpm: libselinux-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsemanage-3.6-5.el9_6.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 123449 checksum: sha256:7ac29f46714cd762f18a52e9807fd1766b0cf9e0388aa3d9befaabf8785a01e3 name: libsemanage evr: 3.6-5.el9_6 sourcerpm: libsemanage-3.6-5.el9_6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsepol-3.6-3.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 338766 checksum: sha256:b98984b2bf42203964cc979ac157df090c63b89a0f5c6560ede01965531c8ffd name: libsepol evr: 3.6-3.el9 sourcerpm: libsepol-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsigsegv-2.13-4.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 30681 checksum: sha256:24005c62017797b612d047a2af83a218633b32302a787fabd22e52230db6adc1 name: libsigsegv evr: 2.13-4.el9 sourcerpm: libsigsegv-2.13-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsmartcols-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 68692 checksum: sha256:c1da912799780b89cd44db4acc318ea4a83adba0d8d99aad1b877879f40dbd48 name: libsmartcols evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libssh-0.10.4-19.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 225821 checksum: sha256:10d0ecb7cef182f8d11eb4bc772943a60c48b8171f602ffd83bb79b9edf5eb44 name: libssh evr: 0.10.4-19.el9_8 sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libssh-config-0.10.4-19.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 14764 checksum: sha256:ce06b99793b9e5abc50a58ebc3ef11037f2efbdb5f819a25f8de4994613194d4 name: libssh-config evr: 0.10.4-19.el9_8 sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libstdc++-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 763021 checksum: sha256:513df35338962e053052b9d52429e8a5f3d60dfe6b1757cd9faaf61d6abda955 name: libstdc++ evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libtasn1-4.16.0-10.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 81418 checksum: sha256:f9473f322407f10205b0db98b89cf8f603e9c769e9977250734136df56cbb981 name: libtasn1 evr: 4.16.0-10.el9_8 sourcerpm: libtasn1-4.16.0-10.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libunistring-0.9.10-15.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 510558 checksum: sha256:6477fb3c3285158f676360e228057e13dc6e983f453c7c74ed4ab140357f9a0d name: libunistring evr: 0.9.10-15.el9 sourcerpm: libunistring-0.9.10-15.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libutempter-1.2.1-6.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 30354 checksum: sha256:0f1df5e0d48c2ac9914bfffa7ed569cd58e42b17ba96bb3f7cf74d1e80de2597 name: libutempter evr: 1.2.1-6.el9 sourcerpm: libutempter-1.2.1-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libuuid-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 32757 checksum: sha256:5694aafca42c707f85af66bba11d102f7636ee17f586466b3ff80254e995ed7b name: libuuid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libverto-0.3.2-3.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 25042 checksum: sha256:7008029afd91af33ca17a22e6eb4ba792fd9b32bee8fb613c79c1527fa6f589a name: libverto evr: 0.3.2-3.el9 sourcerpm: libverto-0.3.2-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libxcrypt-4.4.18-3.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 122599 checksum: sha256:a50bb26a28ee7e6379c86b5b91285299b71569fa87ea968d800a56090b7a179d name: libxcrypt evr: 4.4.18-3.el9 sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libxml2-2.9.13-14.el9_8.5.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 773693 checksum: sha256:d55744c4fe83a63a71906ca41607c5c0deff227a69b957708cc6a37537be4a29 name: libxml2 evr: 2.9.13-14.el9_8.5 sourcerpm: libxml2-2.9.13-14.el9_8.5.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libzstd-1.5.5-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 304135 checksum: sha256:d8a149f0d8f217126642cc4b40199d631b940f7d227191cc2179f3158fd47f9e name: libzstd evr: 1.5.5-1.el9 sourcerpm: zstd-1.5.5-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/lua-libs-5.4.4-4.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 135403 checksum: sha256:9c6c7abe93691e0a6be505199cccab5a41f92ada084faa4f1045ce3932b34d05 name: lua-libs evr: 5.4.4-4.el9 sourcerpm: lua-5.4.4-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/lz4-libs-1.9.3-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 70922 checksum: sha256:9658da838021711f687cf283368664984bfb1c8b9176897d7d477a724a11a731 name: lz4-libs evr: 1.9.3-5.el9 sourcerpm: lz4-1.9.3-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/m/make-4.3-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 553896 checksum: sha256:561f0c2251e9217c81a6c88de4d2d9231a039aaab37e8a0d2559d36ce9fa85fd name: make evr: 1:4.3-8.el9 sourcerpm: make-4.3-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/m/man-db-2.9.3-9.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1239738 checksum: sha256:e808c97dbacfb05e1d9096fb062e1851201307ac80b63f57b561c9234141f550 name: man-db evr: 2.9.3-9.el9 sourcerpm: man-db-2.9.3-9.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/m/mpfr-4.1.0-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 338130 checksum: sha256:4adb12cda3b0e537ba5b22e7615288df751720d2e738bd182675d529cf1ead0c name: mpfr evr: 4.1.0-10.el9 sourcerpm: mpfr-4.1.0-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/n/ncurses-base-6.2-12.20210508.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 97840 checksum: sha256:d62dfd41f9688efa2cf1ceedb96084c63e297fbdcfd1e72bc6757c730092b60c name: ncurses-base evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/n/ncurses-libs-6.2-12.20210508.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 336270 checksum: sha256:f3e1f8e59c7116278aa19b6705a1443f6307d4d6fbdde75a23d2f5d60636cb16 name: ncurses-libs evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openldap-2.6.8-4.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 296805 checksum: sha256:68df8cf8fb4d54c2f1681fa9a030f7af3b179e6dd4fd10ffd7532824121ea74c name: openldap evr: 2.6.8-4.el9 sourcerpm: openldap-2.6.8-4.el9.src.rpm - - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssh-9.9p1-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms - size: 443050 - checksum: sha256:9fad2dc75044e577f03442e524b58223239eba14b12adbf8d14eeb82d22b596e + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssh-9.9p1-12.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 443141 + checksum: sha256:12db3094d78ed82bd7edc6a7cdd2cbf5198da695e293b47b5d00c31ac142aeb7 name: openssh - evr: 9.9p1-11.el9_8 - sourcerpm: openssh-9.9p1-11.el9_8.src.rpm - - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssh-clients-9.9p1-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms - size: 799504 - checksum: sha256:445f8ef1a60229d575547695da0bf2a05fb51408bce01d06548a5273123c8877 + evr: 9.9p1-12.el9_8 + sourcerpm: openssh-9.9p1-12.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssh-clients-9.9p1-12.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 800429 + checksum: sha256:a4307b914ec45f69fea0e17b593ae61597db3e2796ce01a8809c55173f30c121 name: openssh-clients - evr: 9.9p1-11.el9_8 - sourcerpm: openssh-9.9p1-11.el9_8.src.rpm + evr: 9.9p1-12.el9_8 + sourcerpm: openssh-9.9p1-12.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-3.5.8-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1569041 checksum: sha256:2e0d5017032a48c23d4efa43afbe98d45179365eaec16f38e93c271b7728a67a name: openssl evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 14256 checksum: sha256:c00860e9c5a1d90488aa2eb65fe41f62926b38c6b3669d331a8b97e4a60223ac name: openssl-fips-provider evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-fips-provider-so-3.0.7-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 595008 checksum: sha256:60d36ad3a67d6b00e67bb0a19c0902fbb2ecdd873cf7f280a3039d04a092790c name: openssl-fips-provider-so evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-libs-3.5.8-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 2430624 checksum: sha256:3abe1f190415d91d4dc51db74cf2ad7e06b1e5ca5f63dac978fe58d8fd14e493 name: openssl-libs evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/p11-kit-0.26.4-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 625862 checksum: sha256:a00ba14bfd0fc5dd2818f605f2e0520b52ea4b36167504c2523f92a065c2bdaf name: p11-kit evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/p11-kit-trust-0.26.4-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 165521 checksum: sha256:41b84ab0ee4cf914d570a3d648ed98b7de44cef73ea3dfa58ff5eebdec85f42a name: p11-kit-trust evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pam-1.5.1-28.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 645147 checksum: sha256:9285aea93392dada0c8399b7d8a1c85ce7a6fdafd2fb5264727448549c15637c name: pam evr: 1.5.1-28.el9_8.1 sourcerpm: pam-1.5.1-28.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pcre-8.44-4.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 205261 checksum: sha256:e9ddc7d57d4f6e7400b66bcc78b9bafc1f05630e3e0d2a14000bc907f429ddc4 name: pcre evr: 8.44-4.el9 sourcerpm: pcre-8.44-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pcre2-10.40-6.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 241900 checksum: sha256:75db1e5a50e7b1794d7ba18212d95cd2684559da9e7c52eee46490302c7f24dd name: pcre2 evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pcre2-syntax-10.40-6.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 147926 checksum: sha256:d386b5e9b3a4b077b2ba143882e605750855dd3354f13c55fa12ed26908cb442 name: pcre2-syntax evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pkgconf-1.7.3-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 45675 checksum: sha256:bb47b4ecc499c308f41031a99e723827d152d5d750f59849d0c265d820944a26 name: pkgconf evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pkgconf-m4-1.7.3-10.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 16054 checksum: sha256:91bafd6e06099451f60288327b275cfcc651822f6145176a157c6b0fa5131e02 name: pkgconf-m4 evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pkgconf-pkg-config-1.7.3-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 12438 checksum: sha256:9a502d81d73d3303ceb53a06ad7ce525c97117ea64352174a33708bf3429283d name: pkgconf-pkg-config evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/policycoreutils-3.6-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 250930 checksum: sha256:fffd2206493e48409306c0494f53a549fb5e2944a7f53ad7377ed76a4b28f671 name: policycoreutils evr: 3.6-5.el9 sourcerpm: policycoreutils-3.6-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/popt-1.18-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 70397 checksum: sha256:1649240d2a69e13d3b5ddc5c5e63c5d64a77930578a6bc4c3aca32f00423cd87 name: popt evr: 1.18-8.el9 sourcerpm: popt-1.18-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/procps-ng-3.3.17-14.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 361526 checksum: sha256:506ad778f63821e8d9647ca8e0a3ff21b8af9c1666060d5200f9b26ee718333c name: procps-ng evr: 3.3.17-14.el9 sourcerpm: procps-ng-3.3.17-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/publicsuffix-list-dafsa-20210518-3.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 60882 checksum: sha256:e6ec3390a736b085f403168c512a6b2b6f8e12a8fd5a4459f1c7dbbff2b67c33 name: publicsuffix-list-dafsa evr: 20210518-3.el9 sourcerpm: publicsuffix-list-20210518-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-3.9.25-7.el9_8.3.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 33200 checksum: sha256:f0d622eb17a038e98c23ef9bd6961c104c55b3534f69c301b2067cd9161f40f7 name: python3 evr: 3.9.25-7.el9_8.3 sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-libs-3.9.25-7.el9_8.3.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 8483866 checksum: sha256:6227afce7123d2e6c46a4a6d318087c512caebf6e09f7bfc0ba73f7e5853fba4 name: python3-libs evr: 3.9.25-7.el9_8.3 sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-pip-wheel-21.3.1-2.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1198443 checksum: sha256:918041963ad5c3b91276bf1ed3307280673ca8352e2e632bbb3847b33d2bc15a name: python3-pip-wheel evr: 21.3.1-2.el9_8 sourcerpm: python-pip-21.3.1-2.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-setools-4.4.4-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 623460 checksum: sha256:91946d729d2b03b4abe1c43962f22d110468db0163241cda7b1d549c615d0261 name: python3-setools evr: 4.4.4-1.el9 sourcerpm: setools-4.4.4-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-setuptools-53.0.0-15.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 958725 checksum: sha256:e3c5b5927ad0c0bde27a95c54f7a1295965b317d225ece2e98acd365aa45d09f name: python3-setuptools evr: 53.0.0-15.el9 sourcerpm: python-setuptools-53.0.0-15.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-setuptools-wheel-53.0.0-15.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 479203 checksum: sha256:36dacb345e21bc0308ef2508f0c93995520a15ef0b56aab3593186c8dc9c0c5a name: python3-setuptools-wheel evr: 53.0.0-15.el9 sourcerpm: python-setuptools-53.0.0-15.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/r/readline-8.1-4.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 220174 checksum: sha256:01bf315b3bc44c28515c4d33d49173b23d7979d2a09b7b15f749d434b60851e6 name: readline evr: 8.1-4.el9 sourcerpm: readline-8.1-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/r/redhat-release-9.8-1.0.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 61742 checksum: sha256:8157ed988fc34dcfeb6429272959471edd5bde4ac212f26611fd54c180391758 name: redhat-release evr: 9.8-1.0.el9 sourcerpm: redhat-release-9.8-1.0.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/r/rpm-4.16.1.3-40.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 548393 checksum: sha256:53d336d105469fae4b3e4d2c9386c2fe89bc3af64d65b272f05a3e63d62289e8 name: rpm evr: 4.16.1.3-40.el9 sourcerpm: rpm-4.16.1.3-40.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/r/rpm-libs-4.16.1.3-40.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 314878 checksum: sha256:21beeb1c25934cdf549a90d02a87a42e7d0caf91ba1482aa15ff65396171eba0 name: rpm-libs evr: 4.16.1.3-40.el9 sourcerpm: rpm-4.16.1.3-40.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/sed-4.8-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 317456 checksum: sha256:45e246453dc9eb1bad6a71c6f349aad1b1b2e1bf3ec645b80f0ed04fe69c960e name: sed evr: 4.8-10.el9 sourcerpm: sed-4.8-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/setup-2.13.7-10.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 153791 checksum: sha256:0891d395ce067121c28932534237ad1ce231f2bfa987411ad62e73a12d11eb6a name: setup evr: 2.13.7-10.el9 sourcerpm: setup-2.13.7-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/shadow-utils-4.9-16.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1250179 checksum: sha256:17294ee3fbc09c1b5cfc4114d3815cbd92584d6d70a6288e1dce2fda0a62dc59 name: shadow-utils evr: 2:4.9-16.el9 sourcerpm: shadow-utils-4.9-16.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/sqlite-libs-3.34.1-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 665095 checksum: sha256:e5c20e933ec01f746a6c59a94cb99f46c6138dab3f387f0d02dab47f356e2e98 name: sqlite-libs evr: 3.34.1-11.el9_8 sourcerpm: sqlite-3.34.1-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/systemd-libs-252-67.el9_8.6.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 662007 checksum: sha256:d491d7375ed820bf4fa0d5f6d80a6f1a72cfa6ff31e79dabf9288246061cf88e name: systemd-libs evr: 252-67.el9_8.6 sourcerpm: systemd-252-67.el9_8.6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/t/tar-1.34-13.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 914200 checksum: sha256:913d84cd94463e3f0400d80c6742a2974eeab6445ac71ff9eb802a70779c146f name: tar evr: 2:1.34-13.el9_8 sourcerpm: tar-1.34-13.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/t/tcl-8.6.10-7.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1152092 checksum: sha256:2062dce4bed26d3684de4dad68f32307ebacf5c7d50d3aa7bf6470e66fb36df5 name: tcl evr: 1:8.6.10-7.el9 sourcerpm: tcl-8.6.10-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 933286 checksum: sha256:8d6196e02853c0900c3c2e2367665beffb62cb2d0ff5f465ea2d00848a9a35dc name: tzdata evr: 2026c-1.el9_8 sourcerpm: tzdata-2026c-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/u/util-linux-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 2382511 checksum: sha256:35e0b73e574a7d8e93af0adf54adb2303f03423fd5761e357df86288f59d7933 name: util-linux evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/u/util-linux-core-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 476811 checksum: sha256:5575c8fc753d5a81022786dac33e172a6d1602ef41d247a58465754d3f952726 name: util-linux-core evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/v/vim-filesystem-8.2.2637-26.el9_8.21.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 24156 checksum: sha256:4362190912229b1a670e20a7e46d1f20fb2d1d30e80ec22342bba1318784f742 name: vim-filesystem evr: 2:8.2.2637-26.el9_8.21 sourcerpm: vim-8.2.2637-26.el9_8.21.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/x/xz-5.2.5-8.el9_0.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 235693 checksum: sha256:f16d17c26a241400586ddc3d734ce863e3f19d433881ec640a47bedf0dafd07b name: xz evr: 5.2.5-8.el9_0 sourcerpm: xz-5.2.5-8.el9_0.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/x/xz-libs-5.2.5-8.el9_0.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 96649 checksum: sha256:de263f880a4394f04b5e84254ba0a88d781b5bd63665c9e028bc10351490c982 name: xz-libs evr: 5.2.5-8.el9_0 sourcerpm: xz-5.2.5-8.el9_0.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/z/zlib-1.2.11-40.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 95708 checksum: sha256:baf95ffbf40ee014135f16fe33e343faf7ff1ca06509fd97cd988e6afeabf670 name: zlib diff --git a/deploy/konflux/sandbox/rpms.lock.yaml b/deploy/konflux/sandbox/rpms.lock.yaml index 121e1ede0d..0c1a82bb34 100644 --- a/deploy/konflux/sandbox/rpms.lock.yaml +++ b/deploy/konflux/sandbox/rpms.lock.yaml @@ -5,931 +5,931 @@ arches: - arch: aarch64 packages: - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cmake-3.31.8-3.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 11655593 checksum: sha256:2a77fe1c3784083dcdebdd548c16d823b3e4a5adb8d6c00e36841aa633eab53b name: cmake evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cmake-data-3.31.8-3.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 2829291 checksum: sha256:1cdc2e88a996c575b750483c8f562674e4b50a6ab414c7bbe6f6b641c1db7bd9 name: cmake-data evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cmake-filesystem-3.31.8-3.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 19282 checksum: sha256:69a498723354740357fc3f4b4cd235da38fadd98835da193bec0c0850f68f3ad name: cmake-filesystem evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cpp-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 10798392 checksum: sha256:eb16ef369b981c0dca6149e971a63f74ea09c09f1c638086e3cda1e0591ac21b name: cpp evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/e/emacs-filesystem-27.2-18.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 9495 checksum: sha256:49d7b88a05a72c15b78191a987e6def04fda8e2e4ff75711f715d0c0ecadc60f name: emacs-filesystem evr: 1:27.2-18.el9 sourcerpm: emacs-27.2-18.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 31291354 checksum: sha256:542e635ac17b548cc03ab4347438d49f96837c1d91d12714b6b2764ec566156c name: gcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-c++-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 12994215 checksum: sha256:aabe892798a2272d65c269fd6aa14d3b3dfc782c98f92f8fda30c2a4fa33bf6d name: gcc-c++ evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/git-core-2.52.0-1.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 5392428 checksum: sha256:7082917982981bf611c089e7d9d53b63e969af61a37cfd65b5c4081d5b260a1c name: git-core evr: 2.52.0-1.el9 sourcerpm: git-2.52.0-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/glibc-devel-2.34-275.el9_8.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 576947 checksum: sha256:aef79b53955c2ec67efdc39cb91148b377f8719a8b68cf76c7ddcffbcb6b8bf0 name: glibc-devel evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 2925601 checksum: sha256:7e82c856a00206976aede9e7b3865d0a2363b1262c0f9e1949ba29c7b9c47281 name: kernel-headers evr: 5.14.0-687.53.1.el9_8 sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libasan-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 409047 checksum: sha256:854a84e72d40c2a062d112b93f8a694044fd7dc5b3afe7a869a448a12844c3e6 name: libasan evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 67120 checksum: sha256:3763354a5f45d886f9976eec20eb34f8afc2144c69ffba07de546f2820893c70 name: libmpc evr: 1.2.1-4.el9 sourcerpm: libmpc-1.2.1-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libstdc++-devel-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 2520018 checksum: sha256:5a2474c2e817b008212e5a94770d8bfbaad17e9ebba2a38d734907e594ac2aac name: libstdc++-devel evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libtool-ltdl-2.4.6-46.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 37581 checksum: sha256:85f38e641398438f7f08526d7003f47e47a14369798464fd67c465134258e964 name: libtool-ltdl evr: 2.4.6-46.el9 sourcerpm: libtool-2.4.6-46.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libubsan-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 178996 checksum: sha256:d0adfda8f1d8ddf05a46292228e31cf887d731e7999154603e148e3d70d1f182 name: libubsan evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libuv-1.42.0-2.el9_4.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 150129 checksum: sha256:4dc8a40da74e0f9823356460ee11f183c70f382953700fffef0c448198a677cc name: libuv evr: 1:1.42.0-2.el9_4 sourcerpm: libuv-1.42.0-2.el9_4.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libxcrypt-devel-4.4.18-3.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 33051 checksum: sha256:9d621f33df35b9c274b8d65457d6c67fc1522b6c62cf7b2341a4a99f39a93507 name: libxcrypt-devel evr: 4.4.18-3.el9 sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/o/openssl-devel-3.5.8-1.el9_8.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 5039228 checksum: sha256:49aec716fb44d8a07528f59a635d40a47a847b05e6cf57735994e9c2430efa12 name: openssl-devel evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/a/alternatives-1.24-2.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 42137 checksum: sha256:6f7c0667ac015bc0d40836c9f55c73ebf65a209069f69aa8f58e6b4655c820a8 name: alternatives evr: 1.24-2.el9 sourcerpm: chkconfig-1.24-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/a/audit-libs-3.1.5-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 128901 checksum: sha256:11880ec70b575841843cbee0853e03e50a0506321ea0a6f76c0c8145d79ae531 name: audit-libs evr: 3.1.5-8.el9 sourcerpm: audit-3.1.5-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/basesystem-11-13.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 8229 checksum: sha256:f498b0813fa1a825d550e8e3a9e42255eabfa18e6fc96adfc6cc8fa7e16dd513 name: basesystem evr: 11-13.el9 sourcerpm: basesystem-11-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/bash-5.1.8-9.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1760045 checksum: sha256:7dc1febec9c2fb184ed4407f8a188ab267b7e46b3534866f702c6266008ababa name: bash evr: 5.1.8-9.el9 sourcerpm: bash-5.1.8-9.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/binutils-2.35.2-72.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 5021411 checksum: sha256:72e9fd9c4976413060df02e37d358fca208ab144d78e321f448a488d50967155 name: binutils evr: 2.35.2-72.el9 sourcerpm: binutils-2.35.2-72.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/binutils-gold-2.35.2-72.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 908723 checksum: sha256:269bb24ded2f23dcdb74c04597b13281ce6ac47a87a14831ee639d985323bd04 name: binutils-gold evr: 2.35.2-72.el9 sourcerpm: binutils-2.35.2-72.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/bzip2-libs-1.0.8-11.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 47655 checksum: sha256:8267a866b9289ac4e4a92cb4642adcdeff97c2ed816ddda87ed5d5e9d9431a2f name: bzip2-libs evr: 1.0.8-11.el9 sourcerpm: bzip2-1.0.8-11.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/ca-certificates-2025.2.80_v9.0.305-91.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1072208 checksum: sha256:0554bf65d573950e7d550a07bf7d0b3def85ca3b45a7fbde4cce7cadd9a476a7 name: ca-certificates evr: 2025.2.80_v9.0.305-91.el9 sourcerpm: ca-certificates-2025.2.80_v9.0.305-91.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/coreutils-8.32-41.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1175876 checksum: sha256:22827aedd764c1ab706086965859e7f8fef0d719ac0b3d8735f6b8e53b0a13e9 name: coreutils evr: 8.32-41.el9_8.1 sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 2115646 checksum: sha256:b449955249a6d2da7369522a5ac2759919d36633e129ba88924057814906d5f5 name: coreutils-common evr: 8.32-41.el9_8.1 sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/cracklib-2.9.6-28.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 102026 checksum: sha256:d85216b672a15e5dd8cc771b853e3b73e832034949ee23998d8403609fba00a2 name: cracklib evr: 2.9.6-28.el9 sourcerpm: cracklib-2.9.6-28.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/cracklib-dicts-2.9.6-28.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 3829400 checksum: sha256:807345f95c448cb58d4db8d059f76e4c139ef029887d59b431efd20db934745a name: cracklib-dicts evr: 2.9.6-28.el9 sourcerpm: cracklib-2.9.6-28.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/crypto-policies-20260224-1.gitea0f072.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 98707 checksum: sha256:a9ad86b8df75a7c9c29c6f6dd4b8f78cfa1f42b492eff0d11b82c457d37a7f9f name: crypto-policies evr: 20260224-1.gitea0f072.el9_8 sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/cyrus-sasl-lib-2.1.27-22.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 771760 checksum: sha256:7e4f331fc477f0a8482c825ab1b6bfec7f4007481f4eb53fde7fa0ef2d1f6cde name: cyrus-sasl-lib evr: 2.1.27-22.el9 sourcerpm: cyrus-sasl-2.1.27-22.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-debuginfod-client-0.194-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 42824 checksum: sha256:bbcf05d0b46d42c85807d774788edc39528a6898bd880baf11fb77729f59272d name: elfutils-debuginfod-client evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-default-yama-scope-0.194-1.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 8949 checksum: sha256:c473f42c0fec97e2830580110509e4522bc62d9b11761d062499354a0bf58959 name: elfutils-default-yama-scope evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-libelf-0.194-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 203006 checksum: sha256:04ff63b7b669b16827f3688baa0be4a2782f1405db3c6d3ee03c0e4cebc2cdf2 name: elfutils-libelf evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-libs-0.194-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 271645 checksum: sha256:b90a6ad3e465995538785a2536986ad705625daf606d6258bf23d1dd29aec208 name: elfutils-libs evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.5.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 122957 checksum: sha256:d13bd77f429835b303d388e24811fb935060be4788c8fe4cf87703640f2337e0 name: expat evr: 2.5.0-6.el9_8.5 sourcerpm: expat-2.5.0-6.el9_8.5.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/f/filesystem-3.16-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 5003914 checksum: sha256:484bc41109c49066cf350344150abe144e63263e0fafa0bf12c5a47f853e6a49 name: filesystem evr: 3.16-5.el9 sourcerpm: filesystem-3.16-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/f/findutils-4.8.0-7.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 564807 checksum: sha256:158af4d5ecbd8b87f0da762ea1655bd4c86512071a95d8307eda3e0b3991105d name: findutils evr: 1:4.8.0-7.el9 sourcerpm: findutils-4.8.0-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gawk-5.1.0-6.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1026105 checksum: sha256:54365d2a6150079c2dc5003eeb94ada32fc15801b5db05422361f02ed58e6772 name: gawk evr: 5.1.0-6.el9_8.1 sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 60311 checksum: sha256:74fffe15dd7f5a41c7d1990c2804defa1b45fb845da29465b73a81d5866e8a72 name: gdbm-libs evr: 1:1.23-1.el9 sourcerpm: gdbm-1.23-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-2.34-275.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1813928 checksum: sha256:e40a78100f731b5f5a1b235880a0aaad5b08bb32e6521e90535b7f4a94c6e9e9 name: glibc evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-common-2.34-275.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 312665 checksum: sha256:24e1c7189d101531c526dd3cc1a42ab62d89f874824b54fb6b518ec24f190554 name: glibc-common evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-minimal-langpack-2.34-275.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 30881 checksum: sha256:b1348c4c4fe3da979f342b0c27ff0d33a11688274a0b6708292d7750bbc8d853 name: glibc-minimal-langpack evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gmp-6.2.0-13.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 275679 checksum: sha256:df01d909e4613514b1844d6ca26d0bcdff8a659762e507188d04ed046fb0cec4 name: gmp evr: 1:6.2.0-13.el9 sourcerpm: gmp-6.2.0-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/grep-3.6-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 276244 checksum: sha256:583a247a199901d44dc8a96d46010e15f6211f98f7c61ba089825155b0562520 name: grep evr: 3.6-5.el9 sourcerpm: grep-3.6-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gzip-1.12-2.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 171305 checksum: sha256:efafc848fdaa3a8e5e77baddc07abc7d800dc973efd44ecf492bc64dca4fabe6 name: gzip evr: 1.12-2.el9_8 sourcerpm: gzip-1.12-2.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/j/json-c-0.14-11.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 45052 checksum: sha256:fff625bf4f0753eb7323b8933d264f3cc5c992bfecba8b082b204849297149cc name: json-c evr: 0.14-11.el9 sourcerpm: json-c-0.14-11.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/k/keyutils-libs-1.6.3-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 34341 checksum: sha256:d747ed6e1916d8ea400c89ad6078a8c298e30d652ec21985c93539e34c587a73 name: keyutils-libs evr: 1.6.3-1.el9 sourcerpm: keyutils-1.6.3-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/k/krb5-libs-1.21.1-10.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 793489 checksum: sha256:f8bbc9abe0da1ebe5bc028154bd54d465fe0682ddbb64c45882b84ff09f40e1d name: krb5-libs evr: 1.21.1-10.el9_8 sourcerpm: krb5-1.21.1-10.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/less-590-6.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 165028 checksum: sha256:fa762484ba40e0b7eb1c25531a66a0b578b6141cabb6f73d865c13ccdf75c1c9 name: less evr: 590-6.el9 sourcerpm: less-590-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libacl-2.4.0-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 31468 checksum: sha256:70ba010505e9805254f772c3dd9cd9e6176fc9e007e8c9be7204c44f85d8bbd2 name: libacl evr: 2.4.0-1.el9_8 sourcerpm: acl-2.4.0-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libarchive-3.5.3-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 400797 checksum: sha256:7fd7a2981416def9d96892ea11aec3b9af146a95cae11af59a2fdee22fb52516 name: libarchive evr: 3.5.3-11.el9_8 sourcerpm: libarchive-3.5.3-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libatomic-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 26108 checksum: sha256:bebe2e8fd98c64d1667e3de1eb3751b7b641bf5d0cd870ed6445fea5c4526326 name: libatomic evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libattr-2.6.0-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 23276 checksum: sha256:ec08036348dbe2ee41645bdb96eb485b9db5121ec0524258b0639426a22a49bc name: libattr evr: 2.6.0-1.el9_8 sourcerpm: attr-2.6.0-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libblkid-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 113931 checksum: sha256:2c38ac06d3a267b62fc5ea4e149a25c4287c19157f4f18e0f7edea4787b27e15 name: libblkid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libbrotli-1.0.9-9.el9_7.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 325179 checksum: sha256:f5237abc90191238333c1214da97b5202c8a15c2be3ab401ee10d95343cfdf17 name: libbrotli evr: 1.0.9-9.el9_7 sourcerpm: brotli-1.0.9-9.el9_7.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcap-2.48-10.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 78021 checksum: sha256:1ac3014c33b84d7a492b99d46d47940b096e034a3d5886e16ace7159724be012 name: libcap evr: 2.48-10.el9_8.1 sourcerpm: libcap-2.48-10.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcap-ng-0.8.2-7.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 36033 checksum: sha256:dc4eae31749196c0043225c6749e7306ff71f081c09cbdb2fc98a561087c4474 name: libcap-ng evr: 0.8.2-7.el9 sourcerpm: libcap-ng-0.8.2-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcbor-0.7.0-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 59368 checksum: sha256:93a2f44044ab11225b1123bc9df4f4d09c0a5f3251818e7d144ca64fd12c0957 name: libcbor evr: 0.7.0-5.el9 sourcerpm: libcbor-0.7.0-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcom_err-1.46.5-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 26607 checksum: sha256:b7e5c8fe9d9f15864966f46c124659de6cb9137d01e213b3e8cac00d10aab55a name: libcom_err evr: 1.46.5-8.el9 sourcerpm: e2fsprogs-1.46.5-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.7.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 292483 checksum: sha256:408a3da221a1d3856a4b9ab7a7c70901430f71521ef9d05f9b847c0deb27b160 name: libcurl evr: 7.76.1-40.el9_8.7 sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libdb-5.3.28-57.el9_6.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 727417 checksum: sha256:3a912b2a0a6226695a5773138ce5ce090c9fb155151dffe732b8d52e6dd22d63 name: libdb evr: 5.3.28-57.el9_6 sourcerpm: libdb-5.3.28-57.el9_6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libeconf-0.4.1-7.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 32324 checksum: sha256:8a5e117c2690c82835c6013f1fbac37b026f3e953fbffbd84c2f5ef6cf8df571 name: libeconf evr: 0.4.1-7.el9_8 sourcerpm: libeconf-0.4.1-7.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libedit-3.1-39.20210216cvs.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 110092 checksum: sha256:93964f8c06574404f4a5b44781b698f556fbc22f7ae3c82d4d540619772f6816 name: libedit evr: 3.1-39.20210216cvs.el9 sourcerpm: libedit-3.1-39.20210216cvs.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libevent-2.1.13-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 268476 checksum: sha256:27a85302d64c15bfd383929eedcdeabef9bfc8f0a1e085b0501d0de73cb2bf74 name: libevent evr: 2.1.13-1.el9_8 sourcerpm: libevent-2.1.13-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libfdisk-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 156711 checksum: sha256:af043918fc50ce5b3de50c48c3de0143140b692fbf639db6f259270c4211a776 name: libfdisk evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libffi-3.4.2-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 38554 checksum: sha256:d33e180b97a603542cb6f1a78b1c3b0ce4af1bc59ee0bb32620c98a629726bc4 name: libffi evr: 3.4.2-8.el9 sourcerpm: libffi-3.4.2-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libfido2-1.13.0-2.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 100573 checksum: sha256:e56e963635b92f407471c7c5698d602135b135bda4515ecc75ac52dd1d38c7e4 name: libfido2 evr: 1.13.0-2.el9 sourcerpm: libfido2-1.13.0-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgcc-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 81211 checksum: sha256:6923218fdef581189a4b51c7ba158083597f1c6df08cae021a1d90e9d61938a9 name: libgcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgcrypt-1.10.0-13.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 469908 checksum: sha256:4a270fae0cf2f5ad846ce530664bbde72e798bb4a8196afb8fd2db93086c31c9 name: libgcrypt evr: 1.10.0-13.el9_8 sourcerpm: libgcrypt-1.10.0-13.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgomp-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 262138 checksum: sha256:c8b3788fee442304e4158c802ff413df27d394b82f19c0f34ff43b5d3760470c name: libgomp evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgpg-error-1.42-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 222476 checksum: sha256:aee968114aed0238eb26cff42ec9b0819ad32e2bac99aa8124d55b480806aca5 name: libgpg-error evr: 1.42-5.el9 sourcerpm: libgpg-error-1.42-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libidn2-2.3.0-7.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 107549 checksum: sha256:657925cd0fc0abc03cc83ff3688e131a452ea673a5dcb815cd0fc168bf962fc7 name: libidn2 evr: 2.3.0-7.el9 sourcerpm: libidn2-2.3.0-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libmount-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 140081 checksum: sha256:152aee3abc8d97a37ff4ce2f5027d7e16e93ff2c77d25e900258da54e6fcc64e name: libmount evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libnghttp2-1.43.0-6.el9_8.2.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 79650 checksum: sha256:eefb6d331e38314bce55c28e02d67d022c83e41b4d5de91f86d9d846b381ac48 name: libnghttp2 evr: 1.43.0-6.el9_8.2 sourcerpm: nghttp2-1.43.0-6.el9_8.2.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libpkgconf-1.7.3-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 38310 checksum: sha256:9bdfccf6b092e0683aa6984f7c6caa737b30c0b1495e16abb03b5d1a5f8e787a name: libpkgconf evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libpsl-0.21.1-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 67300 checksum: sha256:08968334789ba764986d3beb4745de28eb1e2ed401a03dba9d80e75e3179aa76 name: libpsl evr: 0.21.1-5.el9 sourcerpm: libpsl-0.21.1-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libpwquality-1.4.4-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 125712 checksum: sha256:1657d94bbd79f93dc7a79d474316813bde681ce3a7f62f73314ec4d630e39349 name: libpwquality evr: 1.4.4-8.el9 sourcerpm: libpwquality-1.4.4-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libselinux-3.6-3.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 89531 checksum: sha256:3d7249adbf19206e319cd24acc2e01b0da39975aa3e5af73bdb6c6d438108fac name: libselinux evr: 3.6-3.el9 sourcerpm: libselinux-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsemanage-3.6-5.el9_6.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 120963 checksum: sha256:233d8270827b9166ad11827599800d2a09284d29e73af09c7a12bae251a9463c name: libsemanage evr: 3.6-5.el9_6 sourcerpm: libsemanage-3.6-5.el9_6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsepol-3.6-3.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 326966 checksum: sha256:496ed9e2d7fac9704afe764eab4c2c43b4a47e8c229c14498dd19786f98f80c0 name: libsepol evr: 3.6-3.el9 sourcerpm: libsepol-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsigsegv-2.13-4.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 30566 checksum: sha256:0998ac158161c9d5f3b97c5dc6e35becd84da0ddc5d347a8af581ada529b3b5c name: libsigsegv evr: 2.13-4.el9 sourcerpm: libsigsegv-2.13-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsmartcols-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 67440 checksum: sha256:1704e73a566c920796d877c7bc3e5a96ea0c0c4194109c7b085c1128c01856af name: libsmartcols evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libssh-0.10.4-19.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 223114 checksum: sha256:80f3962d3eb780ca6d6d4f8c6841b003a907d758ad8ad1c3d785e9cf327672f6 name: libssh evr: 0.10.4-19.el9_8 sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libssh-config-0.10.4-19.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 14764 checksum: sha256:ce06b99793b9e5abc50a58ebc3ef11037f2efbdb5f819a25f8de4994613194d4 name: libssh-config evr: 0.10.4-19.el9_8 sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libstdc++-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 723913 checksum: sha256:ae00c5009a2ee4682ec732cde66d3f4fcfc1a7099ba7b3701767d1748a4f2683 name: libstdc++ evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libtasn1-4.16.0-10.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 80446 checksum: sha256:322524934c9b1f0714d2299705dbd41ec93451078e8144babc88c51bd19f6e07 name: libtasn1 evr: 4.16.0-10.el9_8 sourcerpm: libtasn1-4.16.0-10.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libunistring-0.9.10-15.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 503151 checksum: sha256:f68934935fc209e7c595c5619df75f822cc832803e3ea6de2c92e3b91b4d5008 name: libunistring evr: 0.9.10-15.el9 sourcerpm: libunistring-0.9.10-15.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libutempter-1.2.1-6.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 30505 checksum: sha256:d352371cbb7d5bd0c53fc699df953c8c1f184b056690b3c4571e57a6634015c5 name: libutempter evr: 1.2.1-6.el9 sourcerpm: libutempter-1.2.1-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libuuid-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 32555 checksum: sha256:4d7bb4144053a30067a82423fb6e88fd08c7a69bd256eb21b08c0e3f4dbbaee6 name: libuuid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libverto-0.3.2-3.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 24651 checksum: sha256:80d6e32c111ab9c0b2c607475b6a6691cdf6abaec19fde27043e8710a94a8f0c name: libverto evr: 0.3.2-3.el9 sourcerpm: libverto-0.3.2-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libxcrypt-4.4.18-3.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 127655 checksum: sha256:f05030123425a5033bcca3f260313cafc199bc7bca57e9fb13c335bd087c35a7 name: libxcrypt evr: 4.4.18-3.el9 sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libxml2-2.9.13-14.el9_8.5.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 754850 checksum: sha256:39e0ffab5e42aa3688a39a9f27cecb77ee8dbf03682ca5b38c3e15ebc5493863 name: libxml2 evr: 2.9.13-14.el9_8.5 sourcerpm: libxml2-2.9.13-14.el9_8.5.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libzstd-1.5.5-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 283159 checksum: sha256:1229ed44dc7a68278682d7697c41d0abd7daedd242d90c6dc58a9aa6e76f9e6f name: libzstd evr: 1.5.5-1.el9 sourcerpm: zstd-1.5.5-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/lz4-libs-1.9.3-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 70696 checksum: sha256:e1dbd2c38a65b135427c7c8fe988ea70dc95f7e26c4c8177b7dcb23925020015 name: lz4-libs evr: 1.9.3-5.el9 sourcerpm: lz4-1.9.3-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/m/make-4.3-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 550249 checksum: sha256:351a22b0e6744bd329b1b0f22d9c3b69a6da970b575e6c76190cc84b0fe77450 name: make evr: 1:4.3-8.el9 sourcerpm: make-4.3-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/m/mpfr-4.1.0-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 249973 checksum: sha256:c238f7451d1fcc5431bef2904ba56a0bea51d28337ccb692f6d6a09286043a65 name: mpfr evr: 4.1.0-10.el9 sourcerpm: mpfr-4.1.0-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/n/ncurses-base-6.2-12.20210508.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 97840 checksum: sha256:d62dfd41f9688efa2cf1ceedb96084c63e297fbdcfd1e72bc6757c730092b60c name: ncurses-base evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/n/ncurses-libs-6.2-12.20210508.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 324624 checksum: sha256:b5dd452392d2f97bb050c9f5e5376998652c567dcbd8f035d26659b1b551b5c9 name: ncurses-libs evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openldap-2.6.8-4.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 291500 checksum: sha256:fd684316480b2f9a9448d550c2509e37016710ca0724ff3d17d91fa0be2bdc4e name: openldap evr: 2.6.8-4.el9 sourcerpm: openldap-2.6.8-4.el9.src.rpm - - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssh-9.9p1-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms - size: 432812 - checksum: sha256:d8bcf6348f5ee9d840e7f74eaaa53801cb9c48c1184c7254dd06aa73f597c690 + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssh-9.9p1-12.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 432970 + checksum: sha256:e2cc40546db9b067d9d969187aa1fea68ce399ee5f53e44ed91df6f2fc23f49c name: openssh - evr: 9.9p1-11.el9_8 - sourcerpm: openssh-9.9p1-11.el9_8.src.rpm - - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssh-clients-9.9p1-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms - size: 770368 - checksum: sha256:b5f49e9e5d66075859596aa71f62bc8cc951d50129dd43543b6aacd22386b1c1 + evr: 9.9p1-12.el9_8 + sourcerpm: openssh-9.9p1-12.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssh-clients-9.9p1-12.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 770215 + checksum: sha256:8601b26e577d6f8f0726061d96d941a4d8c2d5f0700a2bea5a957bbc17da6c22 name: openssh-clients - evr: 9.9p1-11.el9_8 - sourcerpm: openssh-9.9p1-11.el9_8.src.rpm + evr: 9.9p1-12.el9_8 + sourcerpm: openssh-9.9p1-12.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-3.5.8-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1546056 checksum: sha256:1ef4001b9a9df4695c80e986d3c8ceb7900bb6925f86fde09d16aff9f8d733f5 name: openssl evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 14220 checksum: sha256:158193d2f965db318148ec76e9347b530ac1f5379d849012c0a04d3c50cda478 name: openssl-fips-provider evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-fips-provider-so-3.0.7-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 529340 checksum: sha256:22374a51f8a529dcfcf3b3ebbb2095103e0e811a28953535e5a62e26d1233301 name: openssl-fips-provider-so evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-libs-3.5.8-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 2294194 checksum: sha256:23134af6ea097b94d8261367db01f91604ef3b508654caf4f7399e5c142abcc8 name: openssl-libs evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/p11-kit-0.26.4-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 589112 checksum: sha256:b9391ea6618098782c9325ccda3c9ca8bc0f3b035bd4f113a793cea18026c75e name: p11-kit evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/p11-kit-trust-0.26.4-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 162392 checksum: sha256:a57761123cd5836faf3d40251d16124557ffb452443bfa14cf59ac16e6c99970 name: p11-kit-trust evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pam-1.5.1-28.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 642233 checksum: sha256:12271815e3a5d35dbc4a8e1e3b52442b0d00b833816ef3a923da427a3bca561a name: pam evr: 1.5.1-28.el9_8.1 sourcerpm: pam-1.5.1-28.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pcre-8.44-4.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 187289 checksum: sha256:099feef7e71b82cf0234e37d824fc81353d51dee55694e05181fa686ab50efae name: pcre evr: 8.44-4.el9 sourcerpm: pcre-8.44-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pcre2-10.40-6.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 224938 checksum: sha256:29285f81cef68f73b4f8ff81ee8fdf4ceaa007933302119ed1615e4aa1091613 name: pcre2 evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pcre2-syntax-10.40-6.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 147926 checksum: sha256:d386b5e9b3a4b077b2ba143882e605750855dd3354f13c55fa12ed26908cb442 name: pcre2-syntax evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pkgconf-1.7.3-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 45196 checksum: sha256:aa38a3951a690d721a815ea8f9b01995a85f35a8540d8075205821011d0385e6 name: pkgconf evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pkgconf-m4-1.7.3-10.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 16054 checksum: sha256:91bafd6e06099451f60288327b275cfcc651822f6145176a157c6b0fa5131e02 name: pkgconf-m4 evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pkgconf-pkg-config-1.7.3-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 12398 checksum: sha256:47f1f744f96a2f3d360bc129837738dcebb1ee5032effc4472a891eea1d6a907 name: pkgconf-pkg-config evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/publicsuffix-list-dafsa-20210518-3.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 60882 checksum: sha256:e6ec3390a736b085f403168c512a6b2b6f8e12a8fd5a4459f1c7dbbff2b67c33 name: publicsuffix-list-dafsa evr: 20210518-3.el9 sourcerpm: publicsuffix-list-20210518-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/r/readline-8.1-4.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 219015 checksum: sha256:2ae424b368c6747124b51b205b9e11d74aeaff56b3de90e8cbd36012e0d17707 name: readline evr: 8.1-4.el9 sourcerpm: readline-8.1-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/r/redhat-release-9.8-1.0.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 61683 checksum: sha256:fa7f1d93927c7f8c6f6563a8d221af659074f026e4b12cd74d456b0db1878164 name: redhat-release evr: 9.8-1.0.el9 sourcerpm: redhat-release-9.8-1.0.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/sed-4.8-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 315893 checksum: sha256:b73d314a8ef322a690bb69c49cb0dbd9a5ff18d2ba6b2973e18d2c076a52b62a name: sed evr: 4.8-10.el9 sourcerpm: sed-4.8-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/setup-2.13.7-10.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 153791 checksum: sha256:0891d395ce067121c28932534237ad1ce231f2bfa987411ad62e73a12d11eb6a name: setup evr: 2.13.7-10.el9 sourcerpm: setup-2.13.7-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/shadow-utils-4.9-16.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1244527 checksum: sha256:ccc46a8ea5f30d071e075ad53b5d39d191cfca4614090435528f2d2f944f88a2 name: shadow-utils evr: 2:4.9-16.el9 sourcerpm: shadow-utils-4.9-16.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/systemd-libs-252-67.el9_8.6.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 629233 checksum: sha256:55e58c413ee69f19a69ba25905f96343076e21abe54cdff51f0d80e48be06769 name: systemd-libs evr: 252-67.el9_8.6 sourcerpm: systemd-252-67.el9_8.6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/t/tar-1.34-13.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 909271 checksum: sha256:ee4fa57c4bb87613f6fbd4b785a9e6162d43f046d1bbdd5022771652b931e9d0 name: tar evr: 2:1.34-13.el9_8 sourcerpm: tar-1.34-13.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 933286 checksum: sha256:8d6196e02853c0900c3c2e2367665beffb62cb2d0ff5f465ea2d00848a9a35dc name: tzdata evr: 2026c-1.el9_8 sourcerpm: tzdata-2026c-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/u/util-linux-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 2390152 checksum: sha256:3681bbe37d46309673f366135787f5713f0ef575e3cd413cd221af2512e3634b name: util-linux evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/u/util-linux-core-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 472949 checksum: sha256:de7ad826dd42b383d93f6dc6b720a26d4cd4815d00c0f093c2e28037a8881424 name: util-linux-core evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/v/vim-filesystem-8.2.2637-26.el9_8.21.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 24156 checksum: sha256:4362190912229b1a670e20a7e46d1f20fb2d1d30e80ec22342bba1318784f742 name: vim-filesystem evr: 2:8.2.2637-26.el9_8.21 sourcerpm: vim-8.2.2637-26.el9_8.21.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/x/xz-5.2.5-8.el9_0.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 235798 checksum: sha256:26ac21be6c1e396c7bcbaa9d4786e3275e996d9d78c01f75bbbc6962e6c9bef7 name: xz evr: 5.2.5-8.el9_0 sourcerpm: xz-5.2.5-8.el9_0.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/x/xz-libs-5.2.5-8.el9_0.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 94569 checksum: sha256:06931afb372ed4a6893e51558beaa6b0eab7adda0af93456fd99a081a8b80779 name: xz-libs evr: 5.2.5-8.el9_0 sourcerpm: xz-5.2.5-8.el9_0.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/z/zlib-1.2.11-40.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 94454 checksum: sha256:2e7f193e67235130c10f5579c2d2ec92e22e4098b6d12fb2855d93b1540c60f7 name: zlib evr: 1.2.11-40.el9 sourcerpm: zlib-1.2.11-40.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/codeready-builder/os/Packages/g/glibc-static-2.34-275.el9_8.aarch64.rpm - repoid: ubi-9-codeready-builder-rpms + repoid: codeready-builder-for-ubi-9-aarch64-rpms size: 1249076 checksum: sha256:a683861245c3bbd125301070aca013ed0a7603f6bcf64d99914dcf9ae792b281 name: glibc-static evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/codeready-builder/os/Packages/l/libxcrypt-static-4.4.18-3.el9.aarch64.rpm - repoid: ubi-9-codeready-builder-rpms + repoid: codeready-builder-for-ubi-9-aarch64-rpms size: 111974 checksum: sha256:8bb8a0eb808c389cfec8a0b29ef66a608cc04711b5d4a0ad515f3a32522ae6e0 name: libxcrypt-static @@ -940,917 +940,917 @@ arches: - arch: x86_64 packages: - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cmake-3.31.8-3.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 13989883 checksum: sha256:e67ea7aef1edd470e4ec22982e97871655abcdc0990754d4e8f147d4e7de317a name: cmake evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cmake-data-3.31.8-3.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 2829291 checksum: sha256:1cdc2e88a996c575b750483c8f562674e4b50a6ab414c7bbe6f6b641c1db7bd9 name: cmake-data evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cmake-filesystem-3.31.8-3.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 19309 checksum: sha256:b5ea81385a9e4e6a1ae2bb1175cd774af82f9c570a37008cde873ead466ba5f7 name: cmake-filesystem evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cpp-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 11226193 checksum: sha256:3c0ee1cb8b72f3f5176f8945ab518eb733ccc9f950d317fb5d5ac327d0eb9c90 name: cpp evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/e/emacs-filesystem-27.2-18.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 9495 checksum: sha256:49d7b88a05a72c15b78191a987e6def04fda8e2e4ff75711f715d0c0ecadc60f name: emacs-filesystem evr: 1:27.2-18.el9 sourcerpm: emacs-27.2-18.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 33982584 checksum: sha256:2082784165bbb246b6e5ef5921ed823f0a9709cacdf5823aa60695fd6d4819a2 name: gcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-c++-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 13474286 checksum: sha256:b073d8965ad8eed7520a2a1c9b7c961232511ad9188dcc8c92356160a9d51e37 name: gcc-c++ evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/git-core-2.52.0-1.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 5293286 checksum: sha256:6264aa556d583604f34def3674f5830a70cfee0aac283719e4df295db38acb53 name: git-core evr: 2.52.0-1.el9 sourcerpm: git-2.52.0-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/glibc-devel-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 46499 checksum: sha256:a3b6ed698d21192fa7c421094a5d6648411fba4252db28c96d629778c86e6cd5 name: glibc-devel evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/glibc-headers-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 567171 checksum: sha256:2124192aba2e7931cdf00c2dcd4b70b71b313790c28dcf09b2fb09cc9831c86f name: glibc-headers evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 2965145 checksum: sha256:2473df2cf5b65c762af7941fe87324e98dc0e8b42d1e5745051a0405e200b7f5 name: kernel-headers evr: 5.14.0-687.53.1.el9_8 sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 66075 checksum: sha256:b97b4e98c3c6f41dcfc2ceb4ffa1aba7a338b7cfd9e6c4f63e3160dd3cc033d3 name: libmpc evr: 1.2.1-4.el9 sourcerpm: libmpc-1.2.1-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libstdc++-devel-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 2524816 checksum: sha256:2d031d05fe073adc74919b8372763ae322615d9df23f4a2c642050ab4b389ce5 name: libstdc++-devel evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libtool-ltdl-2.4.6-46.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 38043 checksum: sha256:44f7303229bdb4c2975f9829e3dd13dc7984e2cb53ef0f85baf894b39f605c38 name: libtool-ltdl evr: 2.4.6-46.el9 sourcerpm: libtool-2.4.6-46.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libuv-1.42.0-2.el9_4.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 154427 checksum: sha256:e1fab39251239ccaad2fb4dbe6c55ec1ae60f76d4ae81582b06e6a58e30879b2 name: libuv evr: 1:1.42.0-2.el9_4 sourcerpm: libuv-1.42.0-2.el9_4.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libxcrypt-devel-4.4.18-3.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 33101 checksum: sha256:c1d171391a7d2e043a6953efd3df3e01edc9b4c6cdb54517e1608d204a5fce18 name: libxcrypt-devel evr: 4.4.18-3.el9 sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/o/openssl-devel-3.5.8-1.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 5039851 checksum: sha256:8042a4b1134610ef06c27b7314a5fcd4ce887d1065d4b9e861bc3f647d7fb792 name: openssl-devel evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/a/alternatives-1.24-2.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 42874 checksum: sha256:1c520b9bf7b592d936bb347a5107702e51678e160b88ecfbba6a30e35e47d24e name: alternatives evr: 1.24-2.el9 sourcerpm: chkconfig-1.24-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/a/audit-libs-3.1.5-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 130600 checksum: sha256:637ac2995ce1a6c222772b60f6bc6e6f2829355d2c88dfb1262fb76146d985ae name: audit-libs evr: 3.1.5-8.el9 sourcerpm: audit-3.1.5-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/basesystem-11-13.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 8229 checksum: sha256:f498b0813fa1a825d550e8e3a9e42255eabfa18e6fc96adfc6cc8fa7e16dd513 name: basesystem evr: 11-13.el9 sourcerpm: basesystem-11-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/bash-5.1.8-9.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1769540 checksum: sha256:d3adf8b09aa0bf935c67aa12444e0ee02f70a82c2682bfb2b02bda0a989bb806 name: bash evr: 5.1.8-9.el9 sourcerpm: bash-5.1.8-9.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/binutils-2.35.2-72.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 4821853 checksum: sha256:bda706d43bf47267e31db8ac62fe3206122f97ff035daa6c93ce9cd5063a63ca name: binutils evr: 2.35.2-72.el9 sourcerpm: binutils-2.35.2-72.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/binutils-gold-2.35.2-72.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 758393 checksum: sha256:4d429d1030d8e1c610ba5aea83f8c63090033f440b2c8f788f0e0acd4a3c51b3 name: binutils-gold evr: 2.35.2-72.el9 sourcerpm: binutils-2.35.2-72.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/bzip2-libs-1.0.8-11.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 46333 checksum: sha256:948f763ed17672b8dd83356541e27a53ce97c6df38339c4416a188d452ca4d1e name: bzip2-libs evr: 1.0.8-11.el9 sourcerpm: bzip2-1.0.8-11.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/ca-certificates-2025.2.80_v9.0.305-91.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1072208 checksum: sha256:0554bf65d573950e7d550a07bf7d0b3def85ca3b45a7fbde4cce7cadd9a476a7 name: ca-certificates evr: 2025.2.80_v9.0.305-91.el9 sourcerpm: ca-certificates-2025.2.80_v9.0.305-91.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/coreutils-8.32-41.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1222083 checksum: sha256:374257c4cd69107333a7f524dd99579f3ea3ab842b66168eade0a3475fb6eea1 name: coreutils evr: 8.32-41.el9_8.1 sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 2113503 checksum: sha256:41f69eb8b2087feaa98d0228fb933b6fe8af20a4bf371cfef51e11cbd1f84b4e name: coreutils-common evr: 8.32-41.el9_8.1 sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/cracklib-2.9.6-28.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 102444 checksum: sha256:3b415381d4bd307686268ec42f646c7770f6a815de73a40a88aab7a7061b30a9 name: cracklib evr: 2.9.6-28.el9 sourcerpm: cracklib-2.9.6-28.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/cracklib-dicts-2.9.6-28.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 3829431 checksum: sha256:61c11d3c23b62016b9939f917bb7f7e03cba324eb4ad35b375387ce9f18e25a1 name: cracklib-dicts evr: 2.9.6-28.el9 sourcerpm: cracklib-2.9.6-28.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/crypto-policies-20260224-1.gitea0f072.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 98707 checksum: sha256:a9ad86b8df75a7c9c29c6f6dd4b8f78cfa1f42b492eff0d11b82c457d37a7f9f name: crypto-policies evr: 20260224-1.gitea0f072.el9_8 sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/cyrus-sasl-lib-2.1.27-22.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 786202 checksum: sha256:a85ebdee7a9a49990f87e4709c368212e6a54ecf18c88a3dd54d823a82443898 name: cyrus-sasl-lib evr: 2.1.27-22.el9 sourcerpm: cyrus-sasl-2.1.27-22.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-debuginfod-client-0.194-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 43826 checksum: sha256:1635fd1ecaa9492fa925956dfd56d10063ca336619218f124ab45df0a38b41b0 name: elfutils-debuginfod-client evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-default-yama-scope-0.194-1.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 8949 checksum: sha256:c473f42c0fec97e2830580110509e4522bc62d9b11761d062499354a0bf58959 name: elfutils-default-yama-scope evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-libelf-0.194-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 205864 checksum: sha256:00bbe4776149d8ccedcdf19dd6ceb08c3bb42ff41b98d744abe101af0b11a6c5 name: elfutils-libelf evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-libs-0.194-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 274670 checksum: sha256:ad4f6d425cbc975cead56a2c982e38b0146d00944cbd9b3072d3d1f1271237a5 name: elfutils-libs evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.5.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 129088 checksum: sha256:e6e7edd632fdd1dc4ad94b19b7ab88646f3ef8c1c7956bac58985c3e7118a362 name: expat evr: 2.5.0-6.el9_8.5 sourcerpm: expat-2.5.0-6.el9_8.5.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/f/filesystem-3.16-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 5003807 checksum: sha256:9567592e6e32a9ebd45584cc4feb5d00812f143fcb2d8cd8b1d95108f4f66a2d name: filesystem evr: 3.16-5.el9 sourcerpm: filesystem-3.16-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/f/findutils-4.8.0-7.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 563531 checksum: sha256:a6328afea0a11647b7fb5c48436f0af6c795407bac0650676d3196dd47070de6 name: findutils evr: 1:4.8.0-7.el9 sourcerpm: findutils-4.8.0-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gawk-5.1.0-6.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1046649 checksum: sha256:fcc5e724c32597cf781626728f0faff2f0e5ed6455ab95af4883eefca30a074e name: gawk evr: 5.1.0-6.el9_8.1 sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 60152 checksum: sha256:c8b8346a98d921206666ce740a3647a52ad7a87c2d01d73166165b3e9a789a6c name: gdbm-libs evr: 1:1.23-1.el9 sourcerpm: gdbm-1.23-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 2083064 checksum: sha256:7d2d420b97c05c09ee1e9bd881cb0725fe8d89688b933f411f278cb23210c65d name: glibc evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-common-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 321585 checksum: sha256:f23581b888783f576bd3a55503618a48f74f468fcfd4837bbd7a2d00fe7f3530 name: glibc-common evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-minimal-langpack-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 30913 checksum: sha256:6cc48d78bf2ceacfa5b58633b648c564b66505f4677adea8eb663fe90acd76f2 name: glibc-minimal-langpack evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gmp-6.2.0-13.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 326840 checksum: sha256:d4529445e30b7eb9a8225b0539f70d26d585d7fe306296f948ea73114d1c171f name: gmp evr: 1:6.2.0-13.el9 sourcerpm: gmp-6.2.0-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/grep-3.6-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 279174 checksum: sha256:5556895ff1817066ca71b50785615e944b0fcc7e1c94c983087c7c691819623d name: grep evr: 3.6-5.el9 sourcerpm: grep-3.6-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gzip-1.12-2.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 172571 checksum: sha256:a87bdcce45011f232758c01bd99c564b5f6b549d391646cc573a132d22604b85 name: gzip evr: 1.12-2.el9_8 sourcerpm: gzip-1.12-2.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/j/json-c-0.14-11.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 46136 checksum: sha256:b9bde4162250023103d95908fbca44fff6636a46176f92cf1761c1c3a4580a2f name: json-c evr: 0.14-11.el9 sourcerpm: json-c-0.14-11.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/k/keyutils-libs-1.6.3-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 34363 checksum: sha256:96d75824948387a884d206865db534cd3d46f32422efcb020c20060b59edb27c name: keyutils-libs evr: 1.6.3-1.el9 sourcerpm: keyutils-1.6.3-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/k/krb5-libs-1.21.1-10.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 790743 checksum: sha256:8906be9f2d414c5f4e1218db0c94955c2b3394b43a04b7dbcc2ef66c4340ebc6 name: krb5-libs evr: 1.21.1-10.el9_8 sourcerpm: krb5-1.21.1-10.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/less-590-6.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 166025 checksum: sha256:5bd040f9dd813167935fc390d546c119d90e0a9c77447a3d9ed1ef69c6f5a32a name: less evr: 590-6.el9 sourcerpm: less-590-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libacl-2.4.0-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 31657 checksum: sha256:a81fb7a4d7c946e9bd886ee3c471a4b5040dedbb8ffb2a388120b2094be93cc8 name: libacl evr: 2.4.0-1.el9_8 sourcerpm: acl-2.4.0-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libarchive-3.5.3-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 402853 checksum: sha256:3b8ed4523d8721a1fabc2c92e6871c353b8ff5fb555663ab006fe90a7be35a86 name: libarchive evr: 3.5.3-11.el9_8 sourcerpm: libarchive-3.5.3-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libattr-2.6.0-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 23752 checksum: sha256:9e37537f690c748f7f05faa80966072f118ec722e38ef332fe19cf22b087349f name: libattr evr: 2.6.0-1.el9_8 sourcerpm: attr-2.6.0-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libblkid-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 114192 checksum: sha256:a858400abe83a7955ae509c920f835a950ea2cf1156916823c595a23ba46d536 name: libblkid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libbrotli-1.0.9-9.el9_7.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 326278 checksum: sha256:81096e6aed022489306e2fe1d1496b2b689d8f0bf6c70a94b5bddb82356eeda1 name: libbrotli evr: 1.0.9-9.el9_7 sourcerpm: brotli-1.0.9-9.el9_7.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcap-2.48-10.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 78928 checksum: sha256:d4805439b10fa551b7535cf30ca28d4d5862132c9c429b2b31221bea7f43263a name: libcap evr: 2.48-10.el9_8.1 sourcerpm: libcap-2.48-10.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcap-ng-0.8.2-7.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 36752 checksum: sha256:ebddfc188d1ddbb0d6a238583cbc02dcb9fc0bd063a850b22d48980899976628 name: libcap-ng evr: 0.8.2-7.el9 sourcerpm: libcap-ng-0.8.2-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcbor-0.7.0-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 60575 checksum: sha256:588e8736af3376abfb3cdf372c10baef02c40d916a55958f3bee9767f9ad8526 name: libcbor evr: 0.7.0-5.el9 sourcerpm: libcbor-0.7.0-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcom_err-1.46.5-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 26980 checksum: sha256:b7593ee2d841c69573d8ed553b7416ef727b2c77c0473416a5dadf4b567bf547 name: libcom_err evr: 1.46.5-8.el9 sourcerpm: e2fsprogs-1.46.5-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.7.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 296930 checksum: sha256:044d7a379f0f0f6ed25b9d41439dedfb55b390108ae5a09d4acc6b3565c39ae1 name: libcurl evr: 7.76.1-40.el9_8.7 sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libdb-5.3.28-57.el9_6.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 755192 checksum: sha256:3246e76f197e2b60eb470b9b55d3e0dda2301b029f295fed9c38ff70b87c5b6b name: libdb evr: 5.3.28-57.el9_6 sourcerpm: libdb-5.3.28-57.el9_6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libeconf-0.4.1-7.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 33179 checksum: sha256:a570c5baaedeb1445bc2e4f3930b0a709411bbefbdbf463c3e006cbfc7018894 name: libeconf evr: 0.4.1-7.el9_8 sourcerpm: libeconf-0.4.1-7.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libedit-3.1-39.20210216cvs.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 112056 checksum: sha256:65a730688dfea27934b75af3acf30150c6d254c89d8b68b63233ae7f8b6c9b94 name: libedit evr: 3.1-39.20210216cvs.el9 sourcerpm: libedit-3.1-39.20210216cvs.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libevent-2.1.13-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 276162 checksum: sha256:c28b28573118d5cf0f1f68d96bc7c46d56671e45360698c894ca06ddf40163ae name: libevent evr: 2.1.13-1.el9_8 sourcerpm: libevent-2.1.13-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libfdisk-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 161769 checksum: sha256:1b861f267752e718ce696a80dcc06ef1dde8e9aa73fa2abed3775626d719c124 name: libfdisk evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libffi-3.4.2-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 40619 checksum: sha256:dde0012a94c6f3825e605b095b15767d89c2b87a5da097348310d7e87721c645 name: libffi evr: 3.4.2-8.el9 sourcerpm: libffi-3.4.2-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libfido2-1.13.0-2.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 102746 checksum: sha256:6da940c0528f3e4453db84cb85b402c8f4293a197b1921158df9651edb4845e0 name: libfido2 evr: 1.13.0-2.el9 sourcerpm: libfido2-1.13.0-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgcc-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 87280 checksum: sha256:77c66827ffc14df2f43612b26128b1fd58d5c9597d4d4e564aa239b161272872 name: libgcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgcrypt-1.10.0-13.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 523829 checksum: sha256:c07cd9f613809195b8691d28fd2f3bff55b2a83b9c1cdf4a32c681e0e32dfafb name: libgcrypt evr: 1.10.0-13.el9_8 sourcerpm: libgcrypt-1.10.0-13.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgomp-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 263723 checksum: sha256:87b9a7316760374111290e6e4c76ee4d093566f08a7c7c91ad7827c5948afb69 name: libgomp evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgpg-error-1.42-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 225603 checksum: sha256:8248e20d7a253aa9c0dc7dc3d56b42e1def4fd5753ce8e8b9e980aa664fc9068 name: libgpg-error evr: 1.42-5.el9 sourcerpm: libgpg-error-1.42-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libidn2-2.3.0-7.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 107099 checksum: sha256:055f4ce6b721be7138dc2e45a6586412c65508acea3fe385a2655c129fe264f9 name: libidn2 evr: 2.3.0-7.el9 sourcerpm: libidn2-2.3.0-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libmount-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 142467 checksum: sha256:a9a2022eb9e39301bfcd3273573a108486b2b315c89247f147870016b2e9222c name: libmount evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libnghttp2-1.43.0-6.el9_8.2.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 80410 checksum: sha256:adb3260b6610917c07bda0a6bc521d5628bb1892a66d008f0fea81dc022ac699 name: libnghttp2 evr: 1.43.0-6.el9_8.2 sourcerpm: nghttp2-1.43.0-6.el9_8.2.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libpkgconf-1.7.3-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 38387 checksum: sha256:4feae5941b73640bd86b8d506a657cac5b770043db1464fbcd207721b2159dda name: libpkgconf evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libpsl-0.21.1-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 67454 checksum: sha256:ad1a62ef07682bb64a476c1a49f5cfc7abc9beb44775e7e511bf737e9a6bf99d name: libpsl evr: 0.21.1-5.el9 sourcerpm: libpsl-0.21.1-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libpwquality-1.4.4-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 126104 checksum: sha256:14b7ff2f7fdaf8ebec90261f4619ea7f7c3564c4de8483666de7ed4b1f49b66f name: libpwquality evr: 1.4.4-8.el9 sourcerpm: libpwquality-1.4.4-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libselinux-3.6-3.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 89722 checksum: sha256:ce1cc63a7212c39f5f2a35f719ee38d6418cf081ea78c9317f388d9f41e4a627 name: libselinux evr: 3.6-3.el9 sourcerpm: libselinux-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsemanage-3.6-5.el9_6.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 123449 checksum: sha256:7ac29f46714cd762f18a52e9807fd1766b0cf9e0388aa3d9befaabf8785a01e3 name: libsemanage evr: 3.6-5.el9_6 sourcerpm: libsemanage-3.6-5.el9_6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsepol-3.6-3.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 338766 checksum: sha256:b98984b2bf42203964cc979ac157df090c63b89a0f5c6560ede01965531c8ffd name: libsepol evr: 3.6-3.el9 sourcerpm: libsepol-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsigsegv-2.13-4.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 30681 checksum: sha256:24005c62017797b612d047a2af83a218633b32302a787fabd22e52230db6adc1 name: libsigsegv evr: 2.13-4.el9 sourcerpm: libsigsegv-2.13-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsmartcols-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 68692 checksum: sha256:c1da912799780b89cd44db4acc318ea4a83adba0d8d99aad1b877879f40dbd48 name: libsmartcols evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libssh-0.10.4-19.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 225821 checksum: sha256:10d0ecb7cef182f8d11eb4bc772943a60c48b8171f602ffd83bb79b9edf5eb44 name: libssh evr: 0.10.4-19.el9_8 sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libssh-config-0.10.4-19.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 14764 checksum: sha256:ce06b99793b9e5abc50a58ebc3ef11037f2efbdb5f819a25f8de4994613194d4 name: libssh-config evr: 0.10.4-19.el9_8 sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libstdc++-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 763021 checksum: sha256:513df35338962e053052b9d52429e8a5f3d60dfe6b1757cd9faaf61d6abda955 name: libstdc++ evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libtasn1-4.16.0-10.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 81418 checksum: sha256:f9473f322407f10205b0db98b89cf8f603e9c769e9977250734136df56cbb981 name: libtasn1 evr: 4.16.0-10.el9_8 sourcerpm: libtasn1-4.16.0-10.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libunistring-0.9.10-15.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 510558 checksum: sha256:6477fb3c3285158f676360e228057e13dc6e983f453c7c74ed4ab140357f9a0d name: libunistring evr: 0.9.10-15.el9 sourcerpm: libunistring-0.9.10-15.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libutempter-1.2.1-6.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 30354 checksum: sha256:0f1df5e0d48c2ac9914bfffa7ed569cd58e42b17ba96bb3f7cf74d1e80de2597 name: libutempter evr: 1.2.1-6.el9 sourcerpm: libutempter-1.2.1-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libuuid-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 32757 checksum: sha256:5694aafca42c707f85af66bba11d102f7636ee17f586466b3ff80254e995ed7b name: libuuid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libverto-0.3.2-3.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 25042 checksum: sha256:7008029afd91af33ca17a22e6eb4ba792fd9b32bee8fb613c79c1527fa6f589a name: libverto evr: 0.3.2-3.el9 sourcerpm: libverto-0.3.2-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libxcrypt-4.4.18-3.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 122599 checksum: sha256:a50bb26a28ee7e6379c86b5b91285299b71569fa87ea968d800a56090b7a179d name: libxcrypt evr: 4.4.18-3.el9 sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libxml2-2.9.13-14.el9_8.5.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 773693 checksum: sha256:d55744c4fe83a63a71906ca41607c5c0deff227a69b957708cc6a37537be4a29 name: libxml2 evr: 2.9.13-14.el9_8.5 sourcerpm: libxml2-2.9.13-14.el9_8.5.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libzstd-1.5.5-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 304135 checksum: sha256:d8a149f0d8f217126642cc4b40199d631b940f7d227191cc2179f3158fd47f9e name: libzstd evr: 1.5.5-1.el9 sourcerpm: zstd-1.5.5-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/lz4-libs-1.9.3-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 70922 checksum: sha256:9658da838021711f687cf283368664984bfb1c8b9176897d7d477a724a11a731 name: lz4-libs evr: 1.9.3-5.el9 sourcerpm: lz4-1.9.3-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/m/make-4.3-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 553896 checksum: sha256:561f0c2251e9217c81a6c88de4d2d9231a039aaab37e8a0d2559d36ce9fa85fd name: make evr: 1:4.3-8.el9 sourcerpm: make-4.3-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/m/mpfr-4.1.0-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 338130 checksum: sha256:4adb12cda3b0e537ba5b22e7615288df751720d2e738bd182675d529cf1ead0c name: mpfr evr: 4.1.0-10.el9 sourcerpm: mpfr-4.1.0-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/n/ncurses-base-6.2-12.20210508.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 97840 checksum: sha256:d62dfd41f9688efa2cf1ceedb96084c63e297fbdcfd1e72bc6757c730092b60c name: ncurses-base evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/n/ncurses-libs-6.2-12.20210508.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 336270 checksum: sha256:f3e1f8e59c7116278aa19b6705a1443f6307d4d6fbdde75a23d2f5d60636cb16 name: ncurses-libs evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openldap-2.6.8-4.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 296805 checksum: sha256:68df8cf8fb4d54c2f1681fa9a030f7af3b179e6dd4fd10ffd7532824121ea74c name: openldap evr: 2.6.8-4.el9 sourcerpm: openldap-2.6.8-4.el9.src.rpm - - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssh-9.9p1-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms - size: 443050 - checksum: sha256:9fad2dc75044e577f03442e524b58223239eba14b12adbf8d14eeb82d22b596e + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssh-9.9p1-12.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 443141 + checksum: sha256:12db3094d78ed82bd7edc6a7cdd2cbf5198da695e293b47b5d00c31ac142aeb7 name: openssh - evr: 9.9p1-11.el9_8 - sourcerpm: openssh-9.9p1-11.el9_8.src.rpm - - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssh-clients-9.9p1-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms - size: 799504 - checksum: sha256:445f8ef1a60229d575547695da0bf2a05fb51408bce01d06548a5273123c8877 + evr: 9.9p1-12.el9_8 + sourcerpm: openssh-9.9p1-12.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssh-clients-9.9p1-12.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 800429 + checksum: sha256:a4307b914ec45f69fea0e17b593ae61597db3e2796ce01a8809c55173f30c121 name: openssh-clients - evr: 9.9p1-11.el9_8 - sourcerpm: openssh-9.9p1-11.el9_8.src.rpm + evr: 9.9p1-12.el9_8 + sourcerpm: openssh-9.9p1-12.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-3.5.8-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1569041 checksum: sha256:2e0d5017032a48c23d4efa43afbe98d45179365eaec16f38e93c271b7728a67a name: openssl evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 14256 checksum: sha256:c00860e9c5a1d90488aa2eb65fe41f62926b38c6b3669d331a8b97e4a60223ac name: openssl-fips-provider evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-fips-provider-so-3.0.7-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 595008 checksum: sha256:60d36ad3a67d6b00e67bb0a19c0902fbb2ecdd873cf7f280a3039d04a092790c name: openssl-fips-provider-so evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-libs-3.5.8-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 2430624 checksum: sha256:3abe1f190415d91d4dc51db74cf2ad7e06b1e5ca5f63dac978fe58d8fd14e493 name: openssl-libs evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/p11-kit-0.26.4-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 625862 checksum: sha256:a00ba14bfd0fc5dd2818f605f2e0520b52ea4b36167504c2523f92a065c2bdaf name: p11-kit evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/p11-kit-trust-0.26.4-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 165521 checksum: sha256:41b84ab0ee4cf914d570a3d648ed98b7de44cef73ea3dfa58ff5eebdec85f42a name: p11-kit-trust evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pam-1.5.1-28.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 645147 checksum: sha256:9285aea93392dada0c8399b7d8a1c85ce7a6fdafd2fb5264727448549c15637c name: pam evr: 1.5.1-28.el9_8.1 sourcerpm: pam-1.5.1-28.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pcre-8.44-4.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 205261 checksum: sha256:e9ddc7d57d4f6e7400b66bcc78b9bafc1f05630e3e0d2a14000bc907f429ddc4 name: pcre evr: 8.44-4.el9 sourcerpm: pcre-8.44-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pcre2-10.40-6.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 241900 checksum: sha256:75db1e5a50e7b1794d7ba18212d95cd2684559da9e7c52eee46490302c7f24dd name: pcre2 evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pcre2-syntax-10.40-6.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 147926 checksum: sha256:d386b5e9b3a4b077b2ba143882e605750855dd3354f13c55fa12ed26908cb442 name: pcre2-syntax evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pkgconf-1.7.3-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 45675 checksum: sha256:bb47b4ecc499c308f41031a99e723827d152d5d750f59849d0c265d820944a26 name: pkgconf evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pkgconf-m4-1.7.3-10.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 16054 checksum: sha256:91bafd6e06099451f60288327b275cfcc651822f6145176a157c6b0fa5131e02 name: pkgconf-m4 evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pkgconf-pkg-config-1.7.3-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 12438 checksum: sha256:9a502d81d73d3303ceb53a06ad7ce525c97117ea64352174a33708bf3429283d name: pkgconf-pkg-config evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/publicsuffix-list-dafsa-20210518-3.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 60882 checksum: sha256:e6ec3390a736b085f403168c512a6b2b6f8e12a8fd5a4459f1c7dbbff2b67c33 name: publicsuffix-list-dafsa evr: 20210518-3.el9 sourcerpm: publicsuffix-list-20210518-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/r/readline-8.1-4.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 220174 checksum: sha256:01bf315b3bc44c28515c4d33d49173b23d7979d2a09b7b15f749d434b60851e6 name: readline evr: 8.1-4.el9 sourcerpm: readline-8.1-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/r/redhat-release-9.8-1.0.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 61742 checksum: sha256:8157ed988fc34dcfeb6429272959471edd5bde4ac212f26611fd54c180391758 name: redhat-release evr: 9.8-1.0.el9 sourcerpm: redhat-release-9.8-1.0.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/sed-4.8-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 317456 checksum: sha256:45e246453dc9eb1bad6a71c6f349aad1b1b2e1bf3ec645b80f0ed04fe69c960e name: sed evr: 4.8-10.el9 sourcerpm: sed-4.8-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/setup-2.13.7-10.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 153791 checksum: sha256:0891d395ce067121c28932534237ad1ce231f2bfa987411ad62e73a12d11eb6a name: setup evr: 2.13.7-10.el9 sourcerpm: setup-2.13.7-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/shadow-utils-4.9-16.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1250179 checksum: sha256:17294ee3fbc09c1b5cfc4114d3815cbd92584d6d70a6288e1dce2fda0a62dc59 name: shadow-utils evr: 2:4.9-16.el9 sourcerpm: shadow-utils-4.9-16.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/systemd-libs-252-67.el9_8.6.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 662007 checksum: sha256:d491d7375ed820bf4fa0d5f6d80a6f1a72cfa6ff31e79dabf9288246061cf88e name: systemd-libs evr: 252-67.el9_8.6 sourcerpm: systemd-252-67.el9_8.6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/t/tar-1.34-13.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 914200 checksum: sha256:913d84cd94463e3f0400d80c6742a2974eeab6445ac71ff9eb802a70779c146f name: tar evr: 2:1.34-13.el9_8 sourcerpm: tar-1.34-13.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 933286 checksum: sha256:8d6196e02853c0900c3c2e2367665beffb62cb2d0ff5f465ea2d00848a9a35dc name: tzdata evr: 2026c-1.el9_8 sourcerpm: tzdata-2026c-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/u/util-linux-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 2382511 checksum: sha256:35e0b73e574a7d8e93af0adf54adb2303f03423fd5761e357df86288f59d7933 name: util-linux evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/u/util-linux-core-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 476811 checksum: sha256:5575c8fc753d5a81022786dac33e172a6d1602ef41d247a58465754d3f952726 name: util-linux-core evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/v/vim-filesystem-8.2.2637-26.el9_8.21.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 24156 checksum: sha256:4362190912229b1a670e20a7e46d1f20fb2d1d30e80ec22342bba1318784f742 name: vim-filesystem evr: 2:8.2.2637-26.el9_8.21 sourcerpm: vim-8.2.2637-26.el9_8.21.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/x/xz-5.2.5-8.el9_0.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 235693 checksum: sha256:f16d17c26a241400586ddc3d734ce863e3f19d433881ec640a47bedf0dafd07b name: xz evr: 5.2.5-8.el9_0 sourcerpm: xz-5.2.5-8.el9_0.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/x/xz-libs-5.2.5-8.el9_0.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 96649 checksum: sha256:de263f880a4394f04b5e84254ba0a88d781b5bd63665c9e028bc10351490c982 name: xz-libs evr: 5.2.5-8.el9_0 sourcerpm: xz-5.2.5-8.el9_0.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/z/zlib-1.2.11-40.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 95708 checksum: sha256:baf95ffbf40ee014135f16fe33e343faf7ff1ca06509fd97cd988e6afeabf670 name: zlib evr: 1.2.11-40.el9 sourcerpm: zlib-1.2.11-40.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/codeready-builder/os/Packages/g/glibc-static-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-codeready-builder-rpms + repoid: codeready-builder-for-ubi-9-x86_64-rpms size: 1532701 checksum: sha256:d35de990a70f4a636753a1e45e2e73b6dd66fc46985bbd6695aaac2c68f399a0 name: glibc-static evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/codeready-builder/os/Packages/l/libxcrypt-static-4.4.18-3.el9.x86_64.rpm - repoid: ubi-9-codeready-builder-rpms + repoid: codeready-builder-for-ubi-9-x86_64-rpms size: 105711 checksum: sha256:e2b914f5e136df3c90367dc222e9d893d0c34662f9f2d496b0cadd7d277c579a name: libxcrypt-static diff --git a/deploy/konflux/supervisor/rpms.lock.yaml b/deploy/konflux/supervisor/rpms.lock.yaml index 1f5e623f73..c1b2aaf53b 100644 --- a/deploy/konflux/supervisor/rpms.lock.yaml +++ b/deploy/konflux/supervisor/rpms.lock.yaml @@ -5,959 +5,959 @@ arches: - arch: aarch64 packages: - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cmake-3.31.8-3.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 11655593 checksum: sha256:2a77fe1c3784083dcdebdd548c16d823b3e4a5adb8d6c00e36841aa633eab53b name: cmake evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cmake-data-3.31.8-3.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 2829291 checksum: sha256:1cdc2e88a996c575b750483c8f562674e4b50a6ab414c7bbe6f6b641c1db7bd9 name: cmake-data evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cmake-filesystem-3.31.8-3.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 19282 checksum: sha256:69a498723354740357fc3f4b4cd235da38fadd98835da193bec0c0850f68f3ad name: cmake-filesystem evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cpp-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 10798392 checksum: sha256:eb16ef369b981c0dca6149e971a63f74ea09c09f1c638086e3cda1e0591ac21b name: cpp evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/e/emacs-filesystem-27.2-18.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 9495 checksum: sha256:49d7b88a05a72c15b78191a987e6def04fda8e2e4ff75711f715d0c0ecadc60f name: emacs-filesystem evr: 1:27.2-18.el9 sourcerpm: emacs-27.2-18.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 31291354 checksum: sha256:542e635ac17b548cc03ab4347438d49f96837c1d91d12714b6b2764ec566156c name: gcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-c++-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 12994215 checksum: sha256:aabe892798a2272d65c269fd6aa14d3b3dfc782c98f92f8fda30c2a4fa33bf6d name: gcc-c++ evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/git-core-2.52.0-1.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 5392428 checksum: sha256:7082917982981bf611c089e7d9d53b63e969af61a37cfd65b5c4081d5b260a1c name: git-core evr: 2.52.0-1.el9 sourcerpm: git-2.52.0-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/glibc-devel-2.34-275.el9_8.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 576947 checksum: sha256:aef79b53955c2ec67efdc39cb91148b377f8719a8b68cf76c7ddcffbcb6b8bf0 name: glibc-devel evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 2925601 checksum: sha256:7e82c856a00206976aede9e7b3865d0a2363b1262c0f9e1949ba29c7b9c47281 name: kernel-headers evr: 5.14.0-687.53.1.el9_8 sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libasan-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 409047 checksum: sha256:854a84e72d40c2a062d112b93f8a694044fd7dc5b3afe7a869a448a12844c3e6 name: libasan evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 67120 checksum: sha256:3763354a5f45d886f9976eec20eb34f8afc2144c69ffba07de546f2820893c70 name: libmpc evr: 1.2.1-4.el9 sourcerpm: libmpc-1.2.1-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libstdc++-devel-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 2520018 checksum: sha256:5a2474c2e817b008212e5a94770d8bfbaad17e9ebba2a38d734907e594ac2aac name: libstdc++-devel evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libtool-ltdl-2.4.6-46.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 37581 checksum: sha256:85f38e641398438f7f08526d7003f47e47a14369798464fd67c465134258e964 name: libtool-ltdl evr: 2.4.6-46.el9 sourcerpm: libtool-2.4.6-46.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libubsan-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 178996 checksum: sha256:d0adfda8f1d8ddf05a46292228e31cf887d731e7999154603e148e3d70d1f182 name: libubsan evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libuv-1.42.0-2.el9_4.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 150129 checksum: sha256:4dc8a40da74e0f9823356460ee11f183c70f382953700fffef0c448198a677cc name: libuv evr: 1:1.42.0-2.el9_4 sourcerpm: libuv-1.42.0-2.el9_4.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libxcrypt-devel-4.4.18-3.el9.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 33051 checksum: sha256:9d621f33df35b9c274b8d65457d6c67fc1522b6c62cf7b2341a4a99f39a93507 name: libxcrypt-devel evr: 4.4.18-3.el9 sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/o/openssl-devel-3.5.8-1.el9_8.aarch64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-aarch64-appstream-rpms size: 5039228 checksum: sha256:49aec716fb44d8a07528f59a635d40a47a847b05e6cf57735994e9c2430efa12 name: openssl-devel evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/a/alternatives-1.24-2.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 42137 checksum: sha256:6f7c0667ac015bc0d40836c9f55c73ebf65a209069f69aa8f58e6b4655c820a8 name: alternatives evr: 1.24-2.el9 sourcerpm: chkconfig-1.24-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/a/audit-libs-3.1.5-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 128901 checksum: sha256:11880ec70b575841843cbee0853e03e50a0506321ea0a6f76c0c8145d79ae531 name: audit-libs evr: 3.1.5-8.el9 sourcerpm: audit-3.1.5-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/basesystem-11-13.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 8229 checksum: sha256:f498b0813fa1a825d550e8e3a9e42255eabfa18e6fc96adfc6cc8fa7e16dd513 name: basesystem evr: 11-13.el9 sourcerpm: basesystem-11-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/bash-5.1.8-9.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1760045 checksum: sha256:7dc1febec9c2fb184ed4407f8a188ab267b7e46b3534866f702c6266008ababa name: bash evr: 5.1.8-9.el9 sourcerpm: bash-5.1.8-9.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/binutils-2.35.2-72.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 5021411 checksum: sha256:72e9fd9c4976413060df02e37d358fca208ab144d78e321f448a488d50967155 name: binutils evr: 2.35.2-72.el9 sourcerpm: binutils-2.35.2-72.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/binutils-gold-2.35.2-72.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 908723 checksum: sha256:269bb24ded2f23dcdb74c04597b13281ce6ac47a87a14831ee639d985323bd04 name: binutils-gold evr: 2.35.2-72.el9 sourcerpm: binutils-2.35.2-72.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/bzip2-libs-1.0.8-11.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 47655 checksum: sha256:8267a866b9289ac4e4a92cb4642adcdeff97c2ed816ddda87ed5d5e9d9431a2f name: bzip2-libs evr: 1.0.8-11.el9 sourcerpm: bzip2-1.0.8-11.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/ca-certificates-2025.2.80_v9.0.305-91.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1072208 checksum: sha256:0554bf65d573950e7d550a07bf7d0b3def85ca3b45a7fbde4cce7cadd9a476a7 name: ca-certificates evr: 2025.2.80_v9.0.305-91.el9 sourcerpm: ca-certificates-2025.2.80_v9.0.305-91.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/coreutils-8.32-41.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1175876 checksum: sha256:22827aedd764c1ab706086965859e7f8fef0d719ac0b3d8735f6b8e53b0a13e9 name: coreutils evr: 8.32-41.el9_8.1 sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 2115646 checksum: sha256:b449955249a6d2da7369522a5ac2759919d36633e129ba88924057814906d5f5 name: coreutils-common evr: 8.32-41.el9_8.1 sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/cracklib-2.9.6-28.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 102026 checksum: sha256:d85216b672a15e5dd8cc771b853e3b73e832034949ee23998d8403609fba00a2 name: cracklib evr: 2.9.6-28.el9 sourcerpm: cracklib-2.9.6-28.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/cracklib-dicts-2.9.6-28.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 3829400 checksum: sha256:807345f95c448cb58d4db8d059f76e4c139ef029887d59b431efd20db934745a name: cracklib-dicts evr: 2.9.6-28.el9 sourcerpm: cracklib-2.9.6-28.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/crypto-policies-20260224-1.gitea0f072.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 98707 checksum: sha256:a9ad86b8df75a7c9c29c6f6dd4b8f78cfa1f42b492eff0d11b82c457d37a7f9f name: crypto-policies evr: 20260224-1.gitea0f072.el9_8 sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/crypto-policies-scripts-20260224-1.gitea0f072.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 111065 checksum: sha256:cec44db94e9132008a7fe4e1aa764a29e9c125989c9ac2411f3cf6f8fb669dfd name: crypto-policies-scripts evr: 20260224-1.gitea0f072.el9_8 sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/cyrus-sasl-lib-2.1.27-22.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 771760 checksum: sha256:7e4f331fc477f0a8482c825ab1b6bfec7f4007481f4eb53fde7fa0ef2d1f6cde name: cyrus-sasl-lib evr: 2.1.27-22.el9 sourcerpm: cyrus-sasl-2.1.27-22.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-debuginfod-client-0.194-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 42824 checksum: sha256:bbcf05d0b46d42c85807d774788edc39528a6898bd880baf11fb77729f59272d name: elfutils-debuginfod-client evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-default-yama-scope-0.194-1.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 8949 checksum: sha256:c473f42c0fec97e2830580110509e4522bc62d9b11761d062499354a0bf58959 name: elfutils-default-yama-scope evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-libelf-0.194-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 203006 checksum: sha256:04ff63b7b669b16827f3688baa0be4a2782f1405db3c6d3ee03c0e4cebc2cdf2 name: elfutils-libelf evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-libs-0.194-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 271645 checksum: sha256:b90a6ad3e465995538785a2536986ad705625daf606d6258bf23d1dd29aec208 name: elfutils-libs evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.5.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 122957 checksum: sha256:d13bd77f429835b303d388e24811fb935060be4788c8fe4cf87703640f2337e0 name: expat evr: 2.5.0-6.el9_8.5 sourcerpm: expat-2.5.0-6.el9_8.5.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/f/filesystem-3.16-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 5003914 checksum: sha256:484bc41109c49066cf350344150abe144e63263e0fafa0bf12c5a47f853e6a49 name: filesystem evr: 3.16-5.el9 sourcerpm: filesystem-3.16-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/f/findutils-4.8.0-7.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 564807 checksum: sha256:158af4d5ecbd8b87f0da762ea1655bd4c86512071a95d8307eda3e0b3991105d name: findutils evr: 1:4.8.0-7.el9 sourcerpm: findutils-4.8.0-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gawk-5.1.0-6.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1026105 checksum: sha256:54365d2a6150079c2dc5003eeb94ada32fc15801b5db05422361f02ed58e6772 name: gawk evr: 5.1.0-6.el9_8.1 sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 60311 checksum: sha256:74fffe15dd7f5a41c7d1990c2804defa1b45fb845da29465b73a81d5866e8a72 name: gdbm-libs evr: 1:1.23-1.el9 sourcerpm: gdbm-1.23-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-2.34-275.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1813928 checksum: sha256:e40a78100f731b5f5a1b235880a0aaad5b08bb32e6521e90535b7f4a94c6e9e9 name: glibc evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-common-2.34-275.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 312665 checksum: sha256:24e1c7189d101531c526dd3cc1a42ab62d89f874824b54fb6b518ec24f190554 name: glibc-common evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-minimal-langpack-2.34-275.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 30881 checksum: sha256:b1348c4c4fe3da979f342b0c27ff0d33a11688274a0b6708292d7750bbc8d853 name: glibc-minimal-langpack evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gmp-6.2.0-13.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 275679 checksum: sha256:df01d909e4613514b1844d6ca26d0bcdff8a659762e507188d04ed046fb0cec4 name: gmp evr: 1:6.2.0-13.el9 sourcerpm: gmp-6.2.0-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/grep-3.6-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 276244 checksum: sha256:583a247a199901d44dc8a96d46010e15f6211f98f7c61ba089825155b0562520 name: grep evr: 3.6-5.el9 sourcerpm: grep-3.6-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gzip-1.12-2.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 171305 checksum: sha256:efafc848fdaa3a8e5e77baddc07abc7d800dc973efd44ecf492bc64dca4fabe6 name: gzip evr: 1.12-2.el9_8 sourcerpm: gzip-1.12-2.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/j/json-c-0.14-11.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 45052 checksum: sha256:fff625bf4f0753eb7323b8933d264f3cc5c992bfecba8b082b204849297149cc name: json-c evr: 0.14-11.el9 sourcerpm: json-c-0.14-11.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/k/keyutils-libs-1.6.3-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 34341 checksum: sha256:d747ed6e1916d8ea400c89ad6078a8c298e30d652ec21985c93539e34c587a73 name: keyutils-libs evr: 1.6.3-1.el9 sourcerpm: keyutils-1.6.3-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/k/krb5-libs-1.21.1-10.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 793489 checksum: sha256:f8bbc9abe0da1ebe5bc028154bd54d465fe0682ddbb64c45882b84ff09f40e1d name: krb5-libs evr: 1.21.1-10.el9_8 sourcerpm: krb5-1.21.1-10.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/less-590-6.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 165028 checksum: sha256:fa762484ba40e0b7eb1c25531a66a0b578b6141cabb6f73d865c13ccdf75c1c9 name: less evr: 590-6.el9 sourcerpm: less-590-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libacl-2.4.0-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 31468 checksum: sha256:70ba010505e9805254f772c3dd9cd9e6176fc9e007e8c9be7204c44f85d8bbd2 name: libacl evr: 2.4.0-1.el9_8 sourcerpm: acl-2.4.0-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libarchive-3.5.3-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 400797 checksum: sha256:7fd7a2981416def9d96892ea11aec3b9af146a95cae11af59a2fdee22fb52516 name: libarchive evr: 3.5.3-11.el9_8 sourcerpm: libarchive-3.5.3-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libatomic-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 26108 checksum: sha256:bebe2e8fd98c64d1667e3de1eb3751b7b641bf5d0cd870ed6445fea5c4526326 name: libatomic evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libattr-2.6.0-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 23276 checksum: sha256:ec08036348dbe2ee41645bdb96eb485b9db5121ec0524258b0639426a22a49bc name: libattr evr: 2.6.0-1.el9_8 sourcerpm: attr-2.6.0-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libblkid-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 113931 checksum: sha256:2c38ac06d3a267b62fc5ea4e149a25c4287c19157f4f18e0f7edea4787b27e15 name: libblkid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libbrotli-1.0.9-9.el9_7.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 325179 checksum: sha256:f5237abc90191238333c1214da97b5202c8a15c2be3ab401ee10d95343cfdf17 name: libbrotli evr: 1.0.9-9.el9_7 sourcerpm: brotli-1.0.9-9.el9_7.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcap-2.48-10.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 78021 checksum: sha256:1ac3014c33b84d7a492b99d46d47940b096e034a3d5886e16ace7159724be012 name: libcap evr: 2.48-10.el9_8.1 sourcerpm: libcap-2.48-10.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcap-ng-0.8.2-7.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 36033 checksum: sha256:dc4eae31749196c0043225c6749e7306ff71f081c09cbdb2fc98a561087c4474 name: libcap-ng evr: 0.8.2-7.el9 sourcerpm: libcap-ng-0.8.2-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcbor-0.7.0-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 59368 checksum: sha256:93a2f44044ab11225b1123bc9df4f4d09c0a5f3251818e7d144ca64fd12c0957 name: libcbor evr: 0.7.0-5.el9 sourcerpm: libcbor-0.7.0-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcom_err-1.46.5-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 26607 checksum: sha256:b7e5c8fe9d9f15864966f46c124659de6cb9137d01e213b3e8cac00d10aab55a name: libcom_err evr: 1.46.5-8.el9 sourcerpm: e2fsprogs-1.46.5-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.7.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 292483 checksum: sha256:408a3da221a1d3856a4b9ab7a7c70901430f71521ef9d05f9b847c0deb27b160 name: libcurl evr: 7.76.1-40.el9_8.7 sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libdb-5.3.28-57.el9_6.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 727417 checksum: sha256:3a912b2a0a6226695a5773138ce5ce090c9fb155151dffe732b8d52e6dd22d63 name: libdb evr: 5.3.28-57.el9_6 sourcerpm: libdb-5.3.28-57.el9_6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libeconf-0.4.1-7.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 32324 checksum: sha256:8a5e117c2690c82835c6013f1fbac37b026f3e953fbffbd84c2f5ef6cf8df571 name: libeconf evr: 0.4.1-7.el9_8 sourcerpm: libeconf-0.4.1-7.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libedit-3.1-39.20210216cvs.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 110092 checksum: sha256:93964f8c06574404f4a5b44781b698f556fbc22f7ae3c82d4d540619772f6816 name: libedit evr: 3.1-39.20210216cvs.el9 sourcerpm: libedit-3.1-39.20210216cvs.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libevent-2.1.13-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 268476 checksum: sha256:27a85302d64c15bfd383929eedcdeabef9bfc8f0a1e085b0501d0de73cb2bf74 name: libevent evr: 2.1.13-1.el9_8 sourcerpm: libevent-2.1.13-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libfdisk-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 156711 checksum: sha256:af043918fc50ce5b3de50c48c3de0143140b692fbf639db6f259270c4211a776 name: libfdisk evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libffi-3.4.2-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 38554 checksum: sha256:d33e180b97a603542cb6f1a78b1c3b0ce4af1bc59ee0bb32620c98a629726bc4 name: libffi evr: 3.4.2-8.el9 sourcerpm: libffi-3.4.2-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libfido2-1.13.0-2.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 100573 checksum: sha256:e56e963635b92f407471c7c5698d602135b135bda4515ecc75ac52dd1d38c7e4 name: libfido2 evr: 1.13.0-2.el9 sourcerpm: libfido2-1.13.0-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgcc-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 81211 checksum: sha256:6923218fdef581189a4b51c7ba158083597f1c6df08cae021a1d90e9d61938a9 name: libgcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgcrypt-1.10.0-13.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 469908 checksum: sha256:4a270fae0cf2f5ad846ce530664bbde72e798bb4a8196afb8fd2db93086c31c9 name: libgcrypt evr: 1.10.0-13.el9_8 sourcerpm: libgcrypt-1.10.0-13.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgomp-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 262138 checksum: sha256:c8b3788fee442304e4158c802ff413df27d394b82f19c0f34ff43b5d3760470c name: libgomp evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgpg-error-1.42-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 222476 checksum: sha256:aee968114aed0238eb26cff42ec9b0819ad32e2bac99aa8124d55b480806aca5 name: libgpg-error evr: 1.42-5.el9 sourcerpm: libgpg-error-1.42-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libidn2-2.3.0-7.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 107549 checksum: sha256:657925cd0fc0abc03cc83ff3688e131a452ea673a5dcb815cd0fc168bf962fc7 name: libidn2 evr: 2.3.0-7.el9 sourcerpm: libidn2-2.3.0-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libmount-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 140081 checksum: sha256:152aee3abc8d97a37ff4ce2f5027d7e16e93ff2c77d25e900258da54e6fcc64e name: libmount evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libnghttp2-1.43.0-6.el9_8.2.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 79650 checksum: sha256:eefb6d331e38314bce55c28e02d67d022c83e41b4d5de91f86d9d846b381ac48 name: libnghttp2 evr: 1.43.0-6.el9_8.2 sourcerpm: nghttp2-1.43.0-6.el9_8.2.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libpkgconf-1.7.3-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 38310 checksum: sha256:9bdfccf6b092e0683aa6984f7c6caa737b30c0b1495e16abb03b5d1a5f8e787a name: libpkgconf evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libpsl-0.21.1-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 67300 checksum: sha256:08968334789ba764986d3beb4745de28eb1e2ed401a03dba9d80e75e3179aa76 name: libpsl evr: 0.21.1-5.el9 sourcerpm: libpsl-0.21.1-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libpwquality-1.4.4-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 125712 checksum: sha256:1657d94bbd79f93dc7a79d474316813bde681ce3a7f62f73314ec4d630e39349 name: libpwquality evr: 1.4.4-8.el9 sourcerpm: libpwquality-1.4.4-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libselinux-3.6-3.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 89531 checksum: sha256:3d7249adbf19206e319cd24acc2e01b0da39975aa3e5af73bdb6c6d438108fac name: libselinux evr: 3.6-3.el9 sourcerpm: libselinux-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsemanage-3.6-5.el9_6.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 120963 checksum: sha256:233d8270827b9166ad11827599800d2a09284d29e73af09c7a12bae251a9463c name: libsemanage evr: 3.6-5.el9_6 sourcerpm: libsemanage-3.6-5.el9_6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsepol-3.6-3.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 326966 checksum: sha256:496ed9e2d7fac9704afe764eab4c2c43b4a47e8c229c14498dd19786f98f80c0 name: libsepol evr: 3.6-3.el9 sourcerpm: libsepol-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsigsegv-2.13-4.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 30566 checksum: sha256:0998ac158161c9d5f3b97c5dc6e35becd84da0ddc5d347a8af581ada529b3b5c name: libsigsegv evr: 2.13-4.el9 sourcerpm: libsigsegv-2.13-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsmartcols-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 67440 checksum: sha256:1704e73a566c920796d877c7bc3e5a96ea0c0c4194109c7b085c1128c01856af name: libsmartcols evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libssh-0.10.4-19.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 223114 checksum: sha256:80f3962d3eb780ca6d6d4f8c6841b003a907d758ad8ad1c3d785e9cf327672f6 name: libssh evr: 0.10.4-19.el9_8 sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libssh-config-0.10.4-19.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 14764 checksum: sha256:ce06b99793b9e5abc50a58ebc3ef11037f2efbdb5f819a25f8de4994613194d4 name: libssh-config evr: 0.10.4-19.el9_8 sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libstdc++-11.5.0-14.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 723913 checksum: sha256:ae00c5009a2ee4682ec732cde66d3f4fcfc1a7099ba7b3701767d1748a4f2683 name: libstdc++ evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libtasn1-4.16.0-10.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 80446 checksum: sha256:322524934c9b1f0714d2299705dbd41ec93451078e8144babc88c51bd19f6e07 name: libtasn1 evr: 4.16.0-10.el9_8 sourcerpm: libtasn1-4.16.0-10.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libunistring-0.9.10-15.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 503151 checksum: sha256:f68934935fc209e7c595c5619df75f822cc832803e3ea6de2c92e3b91b4d5008 name: libunistring evr: 0.9.10-15.el9 sourcerpm: libunistring-0.9.10-15.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libutempter-1.2.1-6.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 30505 checksum: sha256:d352371cbb7d5bd0c53fc699df953c8c1f184b056690b3c4571e57a6634015c5 name: libutempter evr: 1.2.1-6.el9 sourcerpm: libutempter-1.2.1-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libuuid-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 32555 checksum: sha256:4d7bb4144053a30067a82423fb6e88fd08c7a69bd256eb21b08c0e3f4dbbaee6 name: libuuid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libverto-0.3.2-3.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 24651 checksum: sha256:80d6e32c111ab9c0b2c607475b6a6691cdf6abaec19fde27043e8710a94a8f0c name: libverto evr: 0.3.2-3.el9 sourcerpm: libverto-0.3.2-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libxcrypt-4.4.18-3.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 127655 checksum: sha256:f05030123425a5033bcca3f260313cafc199bc7bca57e9fb13c335bd087c35a7 name: libxcrypt evr: 4.4.18-3.el9 sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libxml2-2.9.13-14.el9_8.5.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 754850 checksum: sha256:39e0ffab5e42aa3688a39a9f27cecb77ee8dbf03682ca5b38c3e15ebc5493863 name: libxml2 evr: 2.9.13-14.el9_8.5 sourcerpm: libxml2-2.9.13-14.el9_8.5.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libzstd-1.5.5-1.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 283159 checksum: sha256:1229ed44dc7a68278682d7697c41d0abd7daedd242d90c6dc58a9aa6e76f9e6f name: libzstd evr: 1.5.5-1.el9 sourcerpm: zstd-1.5.5-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/lz4-libs-1.9.3-5.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 70696 checksum: sha256:e1dbd2c38a65b135427c7c8fe988ea70dc95f7e26c4c8177b7dcb23925020015 name: lz4-libs evr: 1.9.3-5.el9 sourcerpm: lz4-1.9.3-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/m/make-4.3-8.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 550249 checksum: sha256:351a22b0e6744bd329b1b0f22d9c3b69a6da970b575e6c76190cc84b0fe77450 name: make evr: 1:4.3-8.el9 sourcerpm: make-4.3-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/m/mpfr-4.1.0-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 249973 checksum: sha256:c238f7451d1fcc5431bef2904ba56a0bea51d28337ccb692f6d6a09286043a65 name: mpfr evr: 4.1.0-10.el9 sourcerpm: mpfr-4.1.0-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/n/ncurses-base-6.2-12.20210508.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 97840 checksum: sha256:d62dfd41f9688efa2cf1ceedb96084c63e297fbdcfd1e72bc6757c730092b60c name: ncurses-base evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/n/ncurses-libs-6.2-12.20210508.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 324624 checksum: sha256:b5dd452392d2f97bb050c9f5e5376998652c567dcbd8f035d26659b1b551b5c9 name: ncurses-libs evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openldap-2.6.8-4.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 291500 checksum: sha256:fd684316480b2f9a9448d550c2509e37016710ca0724ff3d17d91fa0be2bdc4e name: openldap evr: 2.6.8-4.el9 sourcerpm: openldap-2.6.8-4.el9.src.rpm - - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssh-9.9p1-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms - size: 432812 - checksum: sha256:d8bcf6348f5ee9d840e7f74eaaa53801cb9c48c1184c7254dd06aa73f597c690 + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssh-9.9p1-12.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 432970 + checksum: sha256:e2cc40546db9b067d9d969187aa1fea68ce399ee5f53e44ed91df6f2fc23f49c name: openssh - evr: 9.9p1-11.el9_8 - sourcerpm: openssh-9.9p1-11.el9_8.src.rpm - - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssh-clients-9.9p1-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms - size: 770368 - checksum: sha256:b5f49e9e5d66075859596aa71f62bc8cc951d50129dd43543b6aacd22386b1c1 + evr: 9.9p1-12.el9_8 + sourcerpm: openssh-9.9p1-12.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssh-clients-9.9p1-12.el9_8.aarch64.rpm + repoid: ubi-9-for-aarch64-baseos-rpms + size: 770215 + checksum: sha256:8601b26e577d6f8f0726061d96d941a4d8c2d5f0700a2bea5a957bbc17da6c22 name: openssh-clients - evr: 9.9p1-11.el9_8 - sourcerpm: openssh-9.9p1-11.el9_8.src.rpm + evr: 9.9p1-12.el9_8 + sourcerpm: openssh-9.9p1-12.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-3.5.8-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1546056 checksum: sha256:1ef4001b9a9df4695c80e986d3c8ceb7900bb6925f86fde09d16aff9f8d733f5 name: openssl evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 14220 checksum: sha256:158193d2f965db318148ec76e9347b530ac1f5379d849012c0a04d3c50cda478 name: openssl-fips-provider evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-fips-provider-so-3.0.7-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 529340 checksum: sha256:22374a51f8a529dcfcf3b3ebbb2095103e0e811a28953535e5a62e26d1233301 name: openssl-fips-provider-so evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-libs-3.5.8-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 2294194 checksum: sha256:23134af6ea097b94d8261367db01f91604ef3b508654caf4f7399e5c142abcc8 name: openssl-libs evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/p11-kit-0.26.4-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 589112 checksum: sha256:b9391ea6618098782c9325ccda3c9ca8bc0f3b035bd4f113a793cea18026c75e name: p11-kit evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/p11-kit-trust-0.26.4-1.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 162392 checksum: sha256:a57761123cd5836faf3d40251d16124557ffb452443bfa14cf59ac16e6c99970 name: p11-kit-trust evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pam-1.5.1-28.el9_8.1.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 642233 checksum: sha256:12271815e3a5d35dbc4a8e1e3b52442b0d00b833816ef3a923da427a3bca561a name: pam evr: 1.5.1-28.el9_8.1 sourcerpm: pam-1.5.1-28.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pcre-8.44-4.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 187289 checksum: sha256:099feef7e71b82cf0234e37d824fc81353d51dee55694e05181fa686ab50efae name: pcre evr: 8.44-4.el9 sourcerpm: pcre-8.44-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pcre2-10.40-6.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 224938 checksum: sha256:29285f81cef68f73b4f8ff81ee8fdf4ceaa007933302119ed1615e4aa1091613 name: pcre2 evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pcre2-syntax-10.40-6.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 147926 checksum: sha256:d386b5e9b3a4b077b2ba143882e605750855dd3354f13c55fa12ed26908cb442 name: pcre2-syntax evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pkgconf-1.7.3-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 45196 checksum: sha256:aa38a3951a690d721a815ea8f9b01995a85f35a8540d8075205821011d0385e6 name: pkgconf evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pkgconf-m4-1.7.3-10.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 16054 checksum: sha256:91bafd6e06099451f60288327b275cfcc651822f6145176a157c6b0fa5131e02 name: pkgconf-m4 evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pkgconf-pkg-config-1.7.3-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 12398 checksum: sha256:47f1f744f96a2f3d360bc129837738dcebb1ee5032effc4472a891eea1d6a907 name: pkgconf-pkg-config evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/publicsuffix-list-dafsa-20210518-3.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 60882 checksum: sha256:e6ec3390a736b085f403168c512a6b2b6f8e12a8fd5a4459f1c7dbbff2b67c33 name: publicsuffix-list-dafsa evr: 20210518-3.el9 sourcerpm: publicsuffix-list-20210518-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-3.9.25-7.el9_8.3.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 33143 checksum: sha256:67e6d2eca7f6558030dd215a4d2d3ede810231faad0f317eb2bcbc7e9ac619ce name: python3 evr: 3.9.25-7.el9_8.3 sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-libs-3.9.25-7.el9_8.3.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 8470487 checksum: sha256:014bbdef3d3d00d09c34df9aaf377337e338e31c4106da6c98b844339fcd50d6 name: python3-libs evr: 3.9.25-7.el9_8.3 sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-pip-wheel-21.3.1-2.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1198443 checksum: sha256:918041963ad5c3b91276bf1ed3307280673ca8352e2e632bbb3847b33d2bc15a name: python3-pip-wheel evr: 21.3.1-2.el9_8 sourcerpm: python-pip-21.3.1-2.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-setuptools-wheel-53.0.0-15.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 479203 checksum: sha256:36dacb345e21bc0308ef2508f0c93995520a15ef0b56aab3593186c8dc9c0c5a name: python3-setuptools-wheel evr: 53.0.0-15.el9 sourcerpm: python-setuptools-53.0.0-15.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/r/readline-8.1-4.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 219015 checksum: sha256:2ae424b368c6747124b51b205b9e11d74aeaff56b3de90e8cbd36012e0d17707 name: readline evr: 8.1-4.el9 sourcerpm: readline-8.1-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/r/redhat-release-9.8-1.0.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 61683 checksum: sha256:fa7f1d93927c7f8c6f6563a8d221af659074f026e4b12cd74d456b0db1878164 name: redhat-release evr: 9.8-1.0.el9 sourcerpm: redhat-release-9.8-1.0.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/sed-4.8-10.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 315893 checksum: sha256:b73d314a8ef322a690bb69c49cb0dbd9a5ff18d2ba6b2973e18d2c076a52b62a name: sed evr: 4.8-10.el9 sourcerpm: sed-4.8-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/setup-2.13.7-10.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 153791 checksum: sha256:0891d395ce067121c28932534237ad1ce231f2bfa987411ad62e73a12d11eb6a name: setup evr: 2.13.7-10.el9 sourcerpm: setup-2.13.7-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/shadow-utils-4.9-16.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 1244527 checksum: sha256:ccc46a8ea5f30d071e075ad53b5d39d191cfca4614090435528f2d2f944f88a2 name: shadow-utils evr: 2:4.9-16.el9 sourcerpm: shadow-utils-4.9-16.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/sqlite-libs-3.34.1-11.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 660770 checksum: sha256:0fbb8043f9c02870c831da433f69b856a6674d02b60306d9cc01149ec89ed239 name: sqlite-libs evr: 3.34.1-11.el9_8 sourcerpm: sqlite-3.34.1-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/systemd-libs-252-67.el9_8.6.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 629233 checksum: sha256:55e58c413ee69f19a69ba25905f96343076e21abe54cdff51f0d80e48be06769 name: systemd-libs evr: 252-67.el9_8.6 sourcerpm: systemd-252-67.el9_8.6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/t/tar-1.34-13.el9_8.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 909271 checksum: sha256:ee4fa57c4bb87613f6fbd4b785a9e6162d43f046d1bbdd5022771652b931e9d0 name: tar evr: 2:1.34-13.el9_8 sourcerpm: tar-1.34-13.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 933286 checksum: sha256:8d6196e02853c0900c3c2e2367665beffb62cb2d0ff5f465ea2d00848a9a35dc name: tzdata evr: 2026c-1.el9_8 sourcerpm: tzdata-2026c-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/u/util-linux-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 2390152 checksum: sha256:3681bbe37d46309673f366135787f5713f0ef575e3cd413cd221af2512e3634b name: util-linux evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/u/util-linux-core-2.37.4-25.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 472949 checksum: sha256:de7ad826dd42b383d93f6dc6b720a26d4cd4815d00c0f093c2e28037a8881424 name: util-linux-core evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/v/vim-filesystem-8.2.2637-26.el9_8.21.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 24156 checksum: sha256:4362190912229b1a670e20a7e46d1f20fb2d1d30e80ec22342bba1318784f742 name: vim-filesystem evr: 2:8.2.2637-26.el9_8.21 sourcerpm: vim-8.2.2637-26.el9_8.21.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/x/xz-5.2.5-8.el9_0.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 235798 checksum: sha256:26ac21be6c1e396c7bcbaa9d4786e3275e996d9d78c01f75bbbc6962e6c9bef7 name: xz evr: 5.2.5-8.el9_0 sourcerpm: xz-5.2.5-8.el9_0.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/x/xz-libs-5.2.5-8.el9_0.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 94569 checksum: sha256:06931afb372ed4a6893e51558beaa6b0eab7adda0af93456fd99a081a8b80779 name: xz-libs evr: 5.2.5-8.el9_0 sourcerpm: xz-5.2.5-8.el9_0.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/z/zlib-1.2.11-40.el9.aarch64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-aarch64-baseos-rpms size: 94454 checksum: sha256:2e7f193e67235130c10f5579c2d2ec92e22e4098b6d12fb2855d93b1540c60f7 name: zlib @@ -968,945 +968,945 @@ arches: - arch: x86_64 packages: - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cmake-3.31.8-3.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 13989883 checksum: sha256:e67ea7aef1edd470e4ec22982e97871655abcdc0990754d4e8f147d4e7de317a name: cmake evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cmake-data-3.31.8-3.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 2829291 checksum: sha256:1cdc2e88a996c575b750483c8f562674e4b50a6ab414c7bbe6f6b641c1db7bd9 name: cmake-data evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cmake-filesystem-3.31.8-3.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 19309 checksum: sha256:b5ea81385a9e4e6a1ae2bb1175cd774af82f9c570a37008cde873ead466ba5f7 name: cmake-filesystem evr: 3.31.8-3.el9 sourcerpm: cmake-3.31.8-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cpp-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 11226193 checksum: sha256:3c0ee1cb8b72f3f5176f8945ab518eb733ccc9f950d317fb5d5ac327d0eb9c90 name: cpp evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/e/emacs-filesystem-27.2-18.el9.noarch.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 9495 checksum: sha256:49d7b88a05a72c15b78191a987e6def04fda8e2e4ff75711f715d0c0ecadc60f name: emacs-filesystem evr: 1:27.2-18.el9 sourcerpm: emacs-27.2-18.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 33982584 checksum: sha256:2082784165bbb246b6e5ef5921ed823f0a9709cacdf5823aa60695fd6d4819a2 name: gcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-c++-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 13474286 checksum: sha256:b073d8965ad8eed7520a2a1c9b7c961232511ad9188dcc8c92356160a9d51e37 name: gcc-c++ evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/git-core-2.52.0-1.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 5293286 checksum: sha256:6264aa556d583604f34def3674f5830a70cfee0aac283719e4df295db38acb53 name: git-core evr: 2.52.0-1.el9 sourcerpm: git-2.52.0-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/glibc-devel-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 46499 checksum: sha256:a3b6ed698d21192fa7c421094a5d6648411fba4252db28c96d629778c86e6cd5 name: glibc-devel evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/glibc-headers-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 567171 checksum: sha256:2124192aba2e7931cdf00c2dcd4b70b71b313790c28dcf09b2fb09cc9831c86f name: glibc-headers evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 2965145 checksum: sha256:2473df2cf5b65c762af7941fe87324e98dc0e8b42d1e5745051a0405e200b7f5 name: kernel-headers evr: 5.14.0-687.53.1.el9_8 sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 66075 checksum: sha256:b97b4e98c3c6f41dcfc2ceb4ffa1aba7a338b7cfd9e6c4f63e3160dd3cc033d3 name: libmpc evr: 1.2.1-4.el9 sourcerpm: libmpc-1.2.1-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libstdc++-devel-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 2524816 checksum: sha256:2d031d05fe073adc74919b8372763ae322615d9df23f4a2c642050ab4b389ce5 name: libstdc++-devel evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libtool-ltdl-2.4.6-46.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 38043 checksum: sha256:44f7303229bdb4c2975f9829e3dd13dc7984e2cb53ef0f85baf894b39f605c38 name: libtool-ltdl evr: 2.4.6-46.el9 sourcerpm: libtool-2.4.6-46.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libuv-1.42.0-2.el9_4.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 154427 checksum: sha256:e1fab39251239ccaad2fb4dbe6c55ec1ae60f76d4ae81582b06e6a58e30879b2 name: libuv evr: 1:1.42.0-2.el9_4 sourcerpm: libuv-1.42.0-2.el9_4.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libxcrypt-devel-4.4.18-3.el9.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 33101 checksum: sha256:c1d171391a7d2e043a6953efd3df3e01edc9b4c6cdb54517e1608d204a5fce18 name: libxcrypt-devel evr: 4.4.18-3.el9 sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/o/openssl-devel-3.5.8-1.el9_8.x86_64.rpm - repoid: ubi-9-appstream-rpms + repoid: ubi-9-for-x86_64-appstream-rpms size: 5039851 checksum: sha256:8042a4b1134610ef06c27b7314a5fcd4ce887d1065d4b9e861bc3f647d7fb792 name: openssl-devel evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/a/alternatives-1.24-2.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 42874 checksum: sha256:1c520b9bf7b592d936bb347a5107702e51678e160b88ecfbba6a30e35e47d24e name: alternatives evr: 1.24-2.el9 sourcerpm: chkconfig-1.24-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/a/audit-libs-3.1.5-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 130600 checksum: sha256:637ac2995ce1a6c222772b60f6bc6e6f2829355d2c88dfb1262fb76146d985ae name: audit-libs evr: 3.1.5-8.el9 sourcerpm: audit-3.1.5-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/basesystem-11-13.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 8229 checksum: sha256:f498b0813fa1a825d550e8e3a9e42255eabfa18e6fc96adfc6cc8fa7e16dd513 name: basesystem evr: 11-13.el9 sourcerpm: basesystem-11-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/bash-5.1.8-9.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1769540 checksum: sha256:d3adf8b09aa0bf935c67aa12444e0ee02f70a82c2682bfb2b02bda0a989bb806 name: bash evr: 5.1.8-9.el9 sourcerpm: bash-5.1.8-9.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/binutils-2.35.2-72.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 4821853 checksum: sha256:bda706d43bf47267e31db8ac62fe3206122f97ff035daa6c93ce9cd5063a63ca name: binutils evr: 2.35.2-72.el9 sourcerpm: binutils-2.35.2-72.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/binutils-gold-2.35.2-72.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 758393 checksum: sha256:4d429d1030d8e1c610ba5aea83f8c63090033f440b2c8f788f0e0acd4a3c51b3 name: binutils-gold evr: 2.35.2-72.el9 sourcerpm: binutils-2.35.2-72.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/bzip2-libs-1.0.8-11.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 46333 checksum: sha256:948f763ed17672b8dd83356541e27a53ce97c6df38339c4416a188d452ca4d1e name: bzip2-libs evr: 1.0.8-11.el9 sourcerpm: bzip2-1.0.8-11.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/ca-certificates-2025.2.80_v9.0.305-91.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1072208 checksum: sha256:0554bf65d573950e7d550a07bf7d0b3def85ca3b45a7fbde4cce7cadd9a476a7 name: ca-certificates evr: 2025.2.80_v9.0.305-91.el9 sourcerpm: ca-certificates-2025.2.80_v9.0.305-91.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/coreutils-8.32-41.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1222083 checksum: sha256:374257c4cd69107333a7f524dd99579f3ea3ab842b66168eade0a3475fb6eea1 name: coreutils evr: 8.32-41.el9_8.1 sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 2113503 checksum: sha256:41f69eb8b2087feaa98d0228fb933b6fe8af20a4bf371cfef51e11cbd1f84b4e name: coreutils-common evr: 8.32-41.el9_8.1 sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/cracklib-2.9.6-28.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 102444 checksum: sha256:3b415381d4bd307686268ec42f646c7770f6a815de73a40a88aab7a7061b30a9 name: cracklib evr: 2.9.6-28.el9 sourcerpm: cracklib-2.9.6-28.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/cracklib-dicts-2.9.6-28.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 3829431 checksum: sha256:61c11d3c23b62016b9939f917bb7f7e03cba324eb4ad35b375387ce9f18e25a1 name: cracklib-dicts evr: 2.9.6-28.el9 sourcerpm: cracklib-2.9.6-28.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/crypto-policies-20260224-1.gitea0f072.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 98707 checksum: sha256:a9ad86b8df75a7c9c29c6f6dd4b8f78cfa1f42b492eff0d11b82c457d37a7f9f name: crypto-policies evr: 20260224-1.gitea0f072.el9_8 sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/crypto-policies-scripts-20260224-1.gitea0f072.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 111065 checksum: sha256:cec44db94e9132008a7fe4e1aa764a29e9c125989c9ac2411f3cf6f8fb669dfd name: crypto-policies-scripts evr: 20260224-1.gitea0f072.el9_8 sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/cyrus-sasl-lib-2.1.27-22.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 786202 checksum: sha256:a85ebdee7a9a49990f87e4709c368212e6a54ecf18c88a3dd54d823a82443898 name: cyrus-sasl-lib evr: 2.1.27-22.el9 sourcerpm: cyrus-sasl-2.1.27-22.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-debuginfod-client-0.194-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 43826 checksum: sha256:1635fd1ecaa9492fa925956dfd56d10063ca336619218f124ab45df0a38b41b0 name: elfutils-debuginfod-client evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-default-yama-scope-0.194-1.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 8949 checksum: sha256:c473f42c0fec97e2830580110509e4522bc62d9b11761d062499354a0bf58959 name: elfutils-default-yama-scope evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-libelf-0.194-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 205864 checksum: sha256:00bbe4776149d8ccedcdf19dd6ceb08c3bb42ff41b98d744abe101af0b11a6c5 name: elfutils-libelf evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-libs-0.194-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 274670 checksum: sha256:ad4f6d425cbc975cead56a2c982e38b0146d00944cbd9b3072d3d1f1271237a5 name: elfutils-libs evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.5.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 129088 checksum: sha256:e6e7edd632fdd1dc4ad94b19b7ab88646f3ef8c1c7956bac58985c3e7118a362 name: expat evr: 2.5.0-6.el9_8.5 sourcerpm: expat-2.5.0-6.el9_8.5.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/f/filesystem-3.16-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 5003807 checksum: sha256:9567592e6e32a9ebd45584cc4feb5d00812f143fcb2d8cd8b1d95108f4f66a2d name: filesystem evr: 3.16-5.el9 sourcerpm: filesystem-3.16-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/f/findutils-4.8.0-7.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 563531 checksum: sha256:a6328afea0a11647b7fb5c48436f0af6c795407bac0650676d3196dd47070de6 name: findutils evr: 1:4.8.0-7.el9 sourcerpm: findutils-4.8.0-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gawk-5.1.0-6.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1046649 checksum: sha256:fcc5e724c32597cf781626728f0faff2f0e5ed6455ab95af4883eefca30a074e name: gawk evr: 5.1.0-6.el9_8.1 sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 60152 checksum: sha256:c8b8346a98d921206666ce740a3647a52ad7a87c2d01d73166165b3e9a789a6c name: gdbm-libs evr: 1:1.23-1.el9 sourcerpm: gdbm-1.23-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 2083064 checksum: sha256:7d2d420b97c05c09ee1e9bd881cb0725fe8d89688b933f411f278cb23210c65d name: glibc evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-common-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 321585 checksum: sha256:f23581b888783f576bd3a55503618a48f74f468fcfd4837bbd7a2d00fe7f3530 name: glibc-common evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-minimal-langpack-2.34-275.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 30913 checksum: sha256:6cc48d78bf2ceacfa5b58633b648c564b66505f4677adea8eb663fe90acd76f2 name: glibc-minimal-langpack evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gmp-6.2.0-13.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 326840 checksum: sha256:d4529445e30b7eb9a8225b0539f70d26d585d7fe306296f948ea73114d1c171f name: gmp evr: 1:6.2.0-13.el9 sourcerpm: gmp-6.2.0-13.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/grep-3.6-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 279174 checksum: sha256:5556895ff1817066ca71b50785615e944b0fcc7e1c94c983087c7c691819623d name: grep evr: 3.6-5.el9 sourcerpm: grep-3.6-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gzip-1.12-2.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 172571 checksum: sha256:a87bdcce45011f232758c01bd99c564b5f6b549d391646cc573a132d22604b85 name: gzip evr: 1.12-2.el9_8 sourcerpm: gzip-1.12-2.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/j/json-c-0.14-11.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 46136 checksum: sha256:b9bde4162250023103d95908fbca44fff6636a46176f92cf1761c1c3a4580a2f name: json-c evr: 0.14-11.el9 sourcerpm: json-c-0.14-11.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/k/keyutils-libs-1.6.3-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 34363 checksum: sha256:96d75824948387a884d206865db534cd3d46f32422efcb020c20060b59edb27c name: keyutils-libs evr: 1.6.3-1.el9 sourcerpm: keyutils-1.6.3-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/k/krb5-libs-1.21.1-10.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 790743 checksum: sha256:8906be9f2d414c5f4e1218db0c94955c2b3394b43a04b7dbcc2ef66c4340ebc6 name: krb5-libs evr: 1.21.1-10.el9_8 sourcerpm: krb5-1.21.1-10.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/less-590-6.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 166025 checksum: sha256:5bd040f9dd813167935fc390d546c119d90e0a9c77447a3d9ed1ef69c6f5a32a name: less evr: 590-6.el9 sourcerpm: less-590-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libacl-2.4.0-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 31657 checksum: sha256:a81fb7a4d7c946e9bd886ee3c471a4b5040dedbb8ffb2a388120b2094be93cc8 name: libacl evr: 2.4.0-1.el9_8 sourcerpm: acl-2.4.0-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libarchive-3.5.3-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 402853 checksum: sha256:3b8ed4523d8721a1fabc2c92e6871c353b8ff5fb555663ab006fe90a7be35a86 name: libarchive evr: 3.5.3-11.el9_8 sourcerpm: libarchive-3.5.3-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libattr-2.6.0-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 23752 checksum: sha256:9e37537f690c748f7f05faa80966072f118ec722e38ef332fe19cf22b087349f name: libattr evr: 2.6.0-1.el9_8 sourcerpm: attr-2.6.0-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libblkid-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 114192 checksum: sha256:a858400abe83a7955ae509c920f835a950ea2cf1156916823c595a23ba46d536 name: libblkid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libbrotli-1.0.9-9.el9_7.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 326278 checksum: sha256:81096e6aed022489306e2fe1d1496b2b689d8f0bf6c70a94b5bddb82356eeda1 name: libbrotli evr: 1.0.9-9.el9_7 sourcerpm: brotli-1.0.9-9.el9_7.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcap-2.48-10.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 78928 checksum: sha256:d4805439b10fa551b7535cf30ca28d4d5862132c9c429b2b31221bea7f43263a name: libcap evr: 2.48-10.el9_8.1 sourcerpm: libcap-2.48-10.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcap-ng-0.8.2-7.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 36752 checksum: sha256:ebddfc188d1ddbb0d6a238583cbc02dcb9fc0bd063a850b22d48980899976628 name: libcap-ng evr: 0.8.2-7.el9 sourcerpm: libcap-ng-0.8.2-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcbor-0.7.0-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 60575 checksum: sha256:588e8736af3376abfb3cdf372c10baef02c40d916a55958f3bee9767f9ad8526 name: libcbor evr: 0.7.0-5.el9 sourcerpm: libcbor-0.7.0-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcom_err-1.46.5-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 26980 checksum: sha256:b7593ee2d841c69573d8ed553b7416ef727b2c77c0473416a5dadf4b567bf547 name: libcom_err evr: 1.46.5-8.el9 sourcerpm: e2fsprogs-1.46.5-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.7.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 296930 checksum: sha256:044d7a379f0f0f6ed25b9d41439dedfb55b390108ae5a09d4acc6b3565c39ae1 name: libcurl evr: 7.76.1-40.el9_8.7 sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libdb-5.3.28-57.el9_6.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 755192 checksum: sha256:3246e76f197e2b60eb470b9b55d3e0dda2301b029f295fed9c38ff70b87c5b6b name: libdb evr: 5.3.28-57.el9_6 sourcerpm: libdb-5.3.28-57.el9_6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libeconf-0.4.1-7.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 33179 checksum: sha256:a570c5baaedeb1445bc2e4f3930b0a709411bbefbdbf463c3e006cbfc7018894 name: libeconf evr: 0.4.1-7.el9_8 sourcerpm: libeconf-0.4.1-7.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libedit-3.1-39.20210216cvs.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 112056 checksum: sha256:65a730688dfea27934b75af3acf30150c6d254c89d8b68b63233ae7f8b6c9b94 name: libedit evr: 3.1-39.20210216cvs.el9 sourcerpm: libedit-3.1-39.20210216cvs.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libevent-2.1.13-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 276162 checksum: sha256:c28b28573118d5cf0f1f68d96bc7c46d56671e45360698c894ca06ddf40163ae name: libevent evr: 2.1.13-1.el9_8 sourcerpm: libevent-2.1.13-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libfdisk-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 161769 checksum: sha256:1b861f267752e718ce696a80dcc06ef1dde8e9aa73fa2abed3775626d719c124 name: libfdisk evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libffi-3.4.2-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 40619 checksum: sha256:dde0012a94c6f3825e605b095b15767d89c2b87a5da097348310d7e87721c645 name: libffi evr: 3.4.2-8.el9 sourcerpm: libffi-3.4.2-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libfido2-1.13.0-2.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 102746 checksum: sha256:6da940c0528f3e4453db84cb85b402c8f4293a197b1921158df9651edb4845e0 name: libfido2 evr: 1.13.0-2.el9 sourcerpm: libfido2-1.13.0-2.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgcc-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 87280 checksum: sha256:77c66827ffc14df2f43612b26128b1fd58d5c9597d4d4e564aa239b161272872 name: libgcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgcrypt-1.10.0-13.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 523829 checksum: sha256:c07cd9f613809195b8691d28fd2f3bff55b2a83b9c1cdf4a32c681e0e32dfafb name: libgcrypt evr: 1.10.0-13.el9_8 sourcerpm: libgcrypt-1.10.0-13.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgomp-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 263723 checksum: sha256:87b9a7316760374111290e6e4c76ee4d093566f08a7c7c91ad7827c5948afb69 name: libgomp evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgpg-error-1.42-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 225603 checksum: sha256:8248e20d7a253aa9c0dc7dc3d56b42e1def4fd5753ce8e8b9e980aa664fc9068 name: libgpg-error evr: 1.42-5.el9 sourcerpm: libgpg-error-1.42-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libidn2-2.3.0-7.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 107099 checksum: sha256:055f4ce6b721be7138dc2e45a6586412c65508acea3fe385a2655c129fe264f9 name: libidn2 evr: 2.3.0-7.el9 sourcerpm: libidn2-2.3.0-7.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libmount-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 142467 checksum: sha256:a9a2022eb9e39301bfcd3273573a108486b2b315c89247f147870016b2e9222c name: libmount evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libnghttp2-1.43.0-6.el9_8.2.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 80410 checksum: sha256:adb3260b6610917c07bda0a6bc521d5628bb1892a66d008f0fea81dc022ac699 name: libnghttp2 evr: 1.43.0-6.el9_8.2 sourcerpm: nghttp2-1.43.0-6.el9_8.2.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libpkgconf-1.7.3-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 38387 checksum: sha256:4feae5941b73640bd86b8d506a657cac5b770043db1464fbcd207721b2159dda name: libpkgconf evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libpsl-0.21.1-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 67454 checksum: sha256:ad1a62ef07682bb64a476c1a49f5cfc7abc9beb44775e7e511bf737e9a6bf99d name: libpsl evr: 0.21.1-5.el9 sourcerpm: libpsl-0.21.1-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libpwquality-1.4.4-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 126104 checksum: sha256:14b7ff2f7fdaf8ebec90261f4619ea7f7c3564c4de8483666de7ed4b1f49b66f name: libpwquality evr: 1.4.4-8.el9 sourcerpm: libpwquality-1.4.4-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libselinux-3.6-3.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 89722 checksum: sha256:ce1cc63a7212c39f5f2a35f719ee38d6418cf081ea78c9317f388d9f41e4a627 name: libselinux evr: 3.6-3.el9 sourcerpm: libselinux-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsemanage-3.6-5.el9_6.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 123449 checksum: sha256:7ac29f46714cd762f18a52e9807fd1766b0cf9e0388aa3d9befaabf8785a01e3 name: libsemanage evr: 3.6-5.el9_6 sourcerpm: libsemanage-3.6-5.el9_6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsepol-3.6-3.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 338766 checksum: sha256:b98984b2bf42203964cc979ac157df090c63b89a0f5c6560ede01965531c8ffd name: libsepol evr: 3.6-3.el9 sourcerpm: libsepol-3.6-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsigsegv-2.13-4.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 30681 checksum: sha256:24005c62017797b612d047a2af83a218633b32302a787fabd22e52230db6adc1 name: libsigsegv evr: 2.13-4.el9 sourcerpm: libsigsegv-2.13-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsmartcols-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 68692 checksum: sha256:c1da912799780b89cd44db4acc318ea4a83adba0d8d99aad1b877879f40dbd48 name: libsmartcols evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libssh-0.10.4-19.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 225821 checksum: sha256:10d0ecb7cef182f8d11eb4bc772943a60c48b8171f602ffd83bb79b9edf5eb44 name: libssh evr: 0.10.4-19.el9_8 sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libssh-config-0.10.4-19.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 14764 checksum: sha256:ce06b99793b9e5abc50a58ebc3ef11037f2efbdb5f819a25f8de4994613194d4 name: libssh-config evr: 0.10.4-19.el9_8 sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libstdc++-11.5.0-14.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 763021 checksum: sha256:513df35338962e053052b9d52429e8a5f3d60dfe6b1757cd9faaf61d6abda955 name: libstdc++ evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libtasn1-4.16.0-10.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 81418 checksum: sha256:f9473f322407f10205b0db98b89cf8f603e9c769e9977250734136df56cbb981 name: libtasn1 evr: 4.16.0-10.el9_8 sourcerpm: libtasn1-4.16.0-10.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libunistring-0.9.10-15.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 510558 checksum: sha256:6477fb3c3285158f676360e228057e13dc6e983f453c7c74ed4ab140357f9a0d name: libunistring evr: 0.9.10-15.el9 sourcerpm: libunistring-0.9.10-15.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libutempter-1.2.1-6.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 30354 checksum: sha256:0f1df5e0d48c2ac9914bfffa7ed569cd58e42b17ba96bb3f7cf74d1e80de2597 name: libutempter evr: 1.2.1-6.el9 sourcerpm: libutempter-1.2.1-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libuuid-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 32757 checksum: sha256:5694aafca42c707f85af66bba11d102f7636ee17f586466b3ff80254e995ed7b name: libuuid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libverto-0.3.2-3.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 25042 checksum: sha256:7008029afd91af33ca17a22e6eb4ba792fd9b32bee8fb613c79c1527fa6f589a name: libverto evr: 0.3.2-3.el9 sourcerpm: libverto-0.3.2-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libxcrypt-4.4.18-3.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 122599 checksum: sha256:a50bb26a28ee7e6379c86b5b91285299b71569fa87ea968d800a56090b7a179d name: libxcrypt evr: 4.4.18-3.el9 sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libxml2-2.9.13-14.el9_8.5.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 773693 checksum: sha256:d55744c4fe83a63a71906ca41607c5c0deff227a69b957708cc6a37537be4a29 name: libxml2 evr: 2.9.13-14.el9_8.5 sourcerpm: libxml2-2.9.13-14.el9_8.5.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libzstd-1.5.5-1.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 304135 checksum: sha256:d8a149f0d8f217126642cc4b40199d631b940f7d227191cc2179f3158fd47f9e name: libzstd evr: 1.5.5-1.el9 sourcerpm: zstd-1.5.5-1.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/lz4-libs-1.9.3-5.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 70922 checksum: sha256:9658da838021711f687cf283368664984bfb1c8b9176897d7d477a724a11a731 name: lz4-libs evr: 1.9.3-5.el9 sourcerpm: lz4-1.9.3-5.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/m/make-4.3-8.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 553896 checksum: sha256:561f0c2251e9217c81a6c88de4d2d9231a039aaab37e8a0d2559d36ce9fa85fd name: make evr: 1:4.3-8.el9 sourcerpm: make-4.3-8.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/m/mpfr-4.1.0-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 338130 checksum: sha256:4adb12cda3b0e537ba5b22e7615288df751720d2e738bd182675d529cf1ead0c name: mpfr evr: 4.1.0-10.el9 sourcerpm: mpfr-4.1.0-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/n/ncurses-base-6.2-12.20210508.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 97840 checksum: sha256:d62dfd41f9688efa2cf1ceedb96084c63e297fbdcfd1e72bc6757c730092b60c name: ncurses-base evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/n/ncurses-libs-6.2-12.20210508.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 336270 checksum: sha256:f3e1f8e59c7116278aa19b6705a1443f6307d4d6fbdde75a23d2f5d60636cb16 name: ncurses-libs evr: 6.2-12.20210508.el9 sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openldap-2.6.8-4.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 296805 checksum: sha256:68df8cf8fb4d54c2f1681fa9a030f7af3b179e6dd4fd10ffd7532824121ea74c name: openldap evr: 2.6.8-4.el9 sourcerpm: openldap-2.6.8-4.el9.src.rpm - - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssh-9.9p1-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms - size: 443050 - checksum: sha256:9fad2dc75044e577f03442e524b58223239eba14b12adbf8d14eeb82d22b596e + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssh-9.9p1-12.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 443141 + checksum: sha256:12db3094d78ed82bd7edc6a7cdd2cbf5198da695e293b47b5d00c31ac142aeb7 name: openssh - evr: 9.9p1-11.el9_8 - sourcerpm: openssh-9.9p1-11.el9_8.src.rpm - - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssh-clients-9.9p1-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms - size: 799504 - checksum: sha256:445f8ef1a60229d575547695da0bf2a05fb51408bce01d06548a5273123c8877 + evr: 9.9p1-12.el9_8 + sourcerpm: openssh-9.9p1-12.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssh-clients-9.9p1-12.el9_8.x86_64.rpm + repoid: ubi-9-for-x86_64-baseos-rpms + size: 800429 + checksum: sha256:a4307b914ec45f69fea0e17b593ae61597db3e2796ce01a8809c55173f30c121 name: openssh-clients - evr: 9.9p1-11.el9_8 - sourcerpm: openssh-9.9p1-11.el9_8.src.rpm + evr: 9.9p1-12.el9_8 + sourcerpm: openssh-9.9p1-12.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-3.5.8-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1569041 checksum: sha256:2e0d5017032a48c23d4efa43afbe98d45179365eaec16f38e93c271b7728a67a name: openssl evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 14256 checksum: sha256:c00860e9c5a1d90488aa2eb65fe41f62926b38c6b3669d331a8b97e4a60223ac name: openssl-fips-provider evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-fips-provider-so-3.0.7-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 595008 checksum: sha256:60d36ad3a67d6b00e67bb0a19c0902fbb2ecdd873cf7f280a3039d04a092790c name: openssl-fips-provider-so evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-libs-3.5.8-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 2430624 checksum: sha256:3abe1f190415d91d4dc51db74cf2ad7e06b1e5ca5f63dac978fe58d8fd14e493 name: openssl-libs evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/p11-kit-0.26.4-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 625862 checksum: sha256:a00ba14bfd0fc5dd2818f605f2e0520b52ea4b36167504c2523f92a065c2bdaf name: p11-kit evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/p11-kit-trust-0.26.4-1.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 165521 checksum: sha256:41b84ab0ee4cf914d570a3d648ed98b7de44cef73ea3dfa58ff5eebdec85f42a name: p11-kit-trust evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pam-1.5.1-28.el9_8.1.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 645147 checksum: sha256:9285aea93392dada0c8399b7d8a1c85ce7a6fdafd2fb5264727448549c15637c name: pam evr: 1.5.1-28.el9_8.1 sourcerpm: pam-1.5.1-28.el9_8.1.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pcre-8.44-4.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 205261 checksum: sha256:e9ddc7d57d4f6e7400b66bcc78b9bafc1f05630e3e0d2a14000bc907f429ddc4 name: pcre evr: 8.44-4.el9 sourcerpm: pcre-8.44-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pcre2-10.40-6.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 241900 checksum: sha256:75db1e5a50e7b1794d7ba18212d95cd2684559da9e7c52eee46490302c7f24dd name: pcre2 evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pcre2-syntax-10.40-6.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 147926 checksum: sha256:d386b5e9b3a4b077b2ba143882e605750855dd3354f13c55fa12ed26908cb442 name: pcre2-syntax evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pkgconf-1.7.3-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 45675 checksum: sha256:bb47b4ecc499c308f41031a99e723827d152d5d750f59849d0c265d820944a26 name: pkgconf evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pkgconf-m4-1.7.3-10.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 16054 checksum: sha256:91bafd6e06099451f60288327b275cfcc651822f6145176a157c6b0fa5131e02 name: pkgconf-m4 evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pkgconf-pkg-config-1.7.3-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 12438 checksum: sha256:9a502d81d73d3303ceb53a06ad7ce525c97117ea64352174a33708bf3429283d name: pkgconf-pkg-config evr: 1.7.3-10.el9 sourcerpm: pkgconf-1.7.3-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/publicsuffix-list-dafsa-20210518-3.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 60882 checksum: sha256:e6ec3390a736b085f403168c512a6b2b6f8e12a8fd5a4459f1c7dbbff2b67c33 name: publicsuffix-list-dafsa evr: 20210518-3.el9 sourcerpm: publicsuffix-list-20210518-3.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-3.9.25-7.el9_8.3.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 33200 checksum: sha256:f0d622eb17a038e98c23ef9bd6961c104c55b3534f69c301b2067cd9161f40f7 name: python3 evr: 3.9.25-7.el9_8.3 sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-libs-3.9.25-7.el9_8.3.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 8483866 checksum: sha256:6227afce7123d2e6c46a4a6d318087c512caebf6e09f7bfc0ba73f7e5853fba4 name: python3-libs evr: 3.9.25-7.el9_8.3 sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-pip-wheel-21.3.1-2.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1198443 checksum: sha256:918041963ad5c3b91276bf1ed3307280673ca8352e2e632bbb3847b33d2bc15a name: python3-pip-wheel evr: 21.3.1-2.el9_8 sourcerpm: python-pip-21.3.1-2.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-setuptools-wheel-53.0.0-15.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 479203 checksum: sha256:36dacb345e21bc0308ef2508f0c93995520a15ef0b56aab3593186c8dc9c0c5a name: python3-setuptools-wheel evr: 53.0.0-15.el9 sourcerpm: python-setuptools-53.0.0-15.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/r/readline-8.1-4.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 220174 checksum: sha256:01bf315b3bc44c28515c4d33d49173b23d7979d2a09b7b15f749d434b60851e6 name: readline evr: 8.1-4.el9 sourcerpm: readline-8.1-4.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/r/redhat-release-9.8-1.0.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 61742 checksum: sha256:8157ed988fc34dcfeb6429272959471edd5bde4ac212f26611fd54c180391758 name: redhat-release evr: 9.8-1.0.el9 sourcerpm: redhat-release-9.8-1.0.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/sed-4.8-10.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 317456 checksum: sha256:45e246453dc9eb1bad6a71c6f349aad1b1b2e1bf3ec645b80f0ed04fe69c960e name: sed evr: 4.8-10.el9 sourcerpm: sed-4.8-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/setup-2.13.7-10.el9.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 153791 checksum: sha256:0891d395ce067121c28932534237ad1ce231f2bfa987411ad62e73a12d11eb6a name: setup evr: 2.13.7-10.el9 sourcerpm: setup-2.13.7-10.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/shadow-utils-4.9-16.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 1250179 checksum: sha256:17294ee3fbc09c1b5cfc4114d3815cbd92584d6d70a6288e1dce2fda0a62dc59 name: shadow-utils evr: 2:4.9-16.el9 sourcerpm: shadow-utils-4.9-16.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/sqlite-libs-3.34.1-11.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 665095 checksum: sha256:e5c20e933ec01f746a6c59a94cb99f46c6138dab3f387f0d02dab47f356e2e98 name: sqlite-libs evr: 3.34.1-11.el9_8 sourcerpm: sqlite-3.34.1-11.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/systemd-libs-252-67.el9_8.6.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 662007 checksum: sha256:d491d7375ed820bf4fa0d5f6d80a6f1a72cfa6ff31e79dabf9288246061cf88e name: systemd-libs evr: 252-67.el9_8.6 sourcerpm: systemd-252-67.el9_8.6.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/t/tar-1.34-13.el9_8.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 914200 checksum: sha256:913d84cd94463e3f0400d80c6742a2974eeab6445ac71ff9eb802a70779c146f name: tar evr: 2:1.34-13.el9_8 sourcerpm: tar-1.34-13.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 933286 checksum: sha256:8d6196e02853c0900c3c2e2367665beffb62cb2d0ff5f465ea2d00848a9a35dc name: tzdata evr: 2026c-1.el9_8 sourcerpm: tzdata-2026c-1.el9_8.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/u/util-linux-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 2382511 checksum: sha256:35e0b73e574a7d8e93af0adf54adb2303f03423fd5761e357df86288f59d7933 name: util-linux evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/u/util-linux-core-2.37.4-25.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 476811 checksum: sha256:5575c8fc753d5a81022786dac33e172a6d1602ef41d247a58465754d3f952726 name: util-linux-core evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/v/vim-filesystem-8.2.2637-26.el9_8.21.noarch.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 24156 checksum: sha256:4362190912229b1a670e20a7e46d1f20fb2d1d30e80ec22342bba1318784f742 name: vim-filesystem evr: 2:8.2.2637-26.el9_8.21 sourcerpm: vim-8.2.2637-26.el9_8.21.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/x/xz-5.2.5-8.el9_0.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 235693 checksum: sha256:f16d17c26a241400586ddc3d734ce863e3f19d433881ec640a47bedf0dafd07b name: xz evr: 5.2.5-8.el9_0 sourcerpm: xz-5.2.5-8.el9_0.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/x/xz-libs-5.2.5-8.el9_0.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 96649 checksum: sha256:de263f880a4394f04b5e84254ba0a88d781b5bd63665c9e028bc10351490c982 name: xz-libs evr: 5.2.5-8.el9_0 sourcerpm: xz-5.2.5-8.el9_0.src.rpm - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/z/zlib-1.2.11-40.el9.x86_64.rpm - repoid: ubi-9-baseos-rpms + repoid: ubi-9-for-x86_64-baseos-rpms size: 95708 checksum: sha256:baf95ffbf40ee014135f16fe33e343faf7ff1ca06509fd97cd988e6afeabf670 name: zlib From dde8a9a57f34f9d998618b3d35821608165c980f Mon Sep 17 00:00:00 2001 From: krishicks Date: Wed, 30 Sep 2026 22:54:42 +0000 Subject: [PATCH 09/33] fix(server): log polled request responses at debug (#3974) log_response has always logged every gateway response at INFO, including health probes and the GetSandboxConfig and provider-readiness polls each supervisor makes. #3915 demoted the request spans for those polled paths to DEBUG, which stripped the request{method path} prefix from the log line at INFO but left the line itself, so the gateway log fills with bare 'response status=200' lines several times per second. Follow the span's level: polled requests log their response at DEBUG, or WARN on a 5xx so probe and poll failures stay visible. Signed-off-by: Kris Hicks --- crates/openshell-server/src/multiplex.rs | 62 ++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/crates/openshell-server/src/multiplex.rs b/crates/openshell-server/src/multiplex.rs index a909ff2bb7..7f175a89ad 100644 --- a/crates/openshell-server/src/multiplex.rs +++ b/crates/openshell-server/src/multiplex.rs @@ -145,11 +145,17 @@ fn log_response(res: &Response, latency: Duration, span: &Span) { if status.is_server_error() { crate::otel_tracing::mark_error(span); } - tracing::info!( - status = status.as_u16(), - latency_ms = latency.as_millis(), - "response" - ); + // Polled requests get a DEBUG span, which is `None` when filtered out. + let polled = span + .metadata() + .is_none_or(|m| *m.level() == tracing::Level::DEBUG); + let server_error = status.is_server_error(); + let (status, latency_ms) = (status.as_u16(), latency.as_millis()); + match (polled, server_error) { + (false, _) => tracing::info!(status, latency_ms, "response"), + (true, true) => tracing::warn!(status, latency_ms, "response"), + (true, false) => tracing::debug!(status, latency_ms, "response"), + } } fn record_response_trailers( @@ -2261,6 +2267,52 @@ mod tests { ); } + #[test] + fn polled_path_responses_log_at_debug() { + let log_buf: Arc>> = Arc::new(Mutex::new(Vec::new())); + let writer = TraceBuf(log_buf.clone()); + let subscriber = { + use tracing_subscriber::layer::SubscriberExt as _; + tracing_subscriber::registry() + .with(tracing_subscriber::filter::LevelFilter::INFO) + .with( + tracing_subscriber::fmt::layer() + .with_writer(move || writer.clone()) + .with_ansi(false), + ) + }; + { + let _traced = crate::otel_tracing::test_exporter::install_scoped(subscriber); + let respond = |path: &str, status: u16| { + let req = Request::builder() + .uri(path) + .body(Empty::::new()) + .unwrap(); + let res = Response::builder() + .status(status) + .body(Empty::::new()) + .unwrap(); + log_response(&res, Duration::from_millis(1), &make_request_span(&req)); + }; + respond("/openshell.v1.OpenShell/GetSandboxConfig", 200); + respond("/healthz", 200); + respond("/openshell.v1.OpenShell/CreateSandbox", 201); + respond("/healthz", 503); + } + + let output = String::from_utf8(log_buf.lock().unwrap().clone()).unwrap(); + let lines: Vec<&str> = output.lines().collect(); + assert_eq!(lines.len(), 2, "got: {output}"); + assert!( + lines[0].contains("INFO") && lines[0].contains("status=201"), + "got: {output}" + ); + assert!( + lines[1].contains("WARN") && lines[1].contains("status=503"), + "got: {output}" + ); + } + /// The `TraceLayer` creates the server span, so no gRPC handler needs /// `#[instrument]`. The request ID carries into it so a trace can be /// correlated with the gateway's logs. From 4784e7945168669b5b2410fb1d0196055c802235 Mon Sep 17 00:00:00 2001 From: Matthew Grossman Date: Wed, 30 Sep 2026 23:04:50 +0000 Subject: [PATCH 10/33] refactor(sandbox): remove unreachable root-side identity and workspace code (#3979) * refactor(sandbox): remove unreachable root-side identity and workspace code RFC 0012 moved the workload into its own capability-free container that starts as the final sandbox identity. The sandbox no longer runs a root supervisor that prepares the filesystem, rewrites account files, resolves OCI USER entries, or drops privileges before launching the workload, so that code had no production callers. Remove the unreachable paths and their tests: - prepare_filesystem / prepare_filesystem_with_identity, the /sandbox and OCI workspace chown preparation, and the root-side workspace validation (validate_oci_workspace and its privilege-dropped subprocess) - the hidden validate-workspace subcommand - drop_privileges / drop_privileges_with_identity, capability bounding set clearing, validate_sandbox_user/group, and /etc/passwd and /etc/group rewriting - the sandbox-side OCI USER resolver (identity.rs) and ResolvedProcessIdentity; the boundary now writes the driver-resolved UID/GID into the policy directly The workspace check that still runs inside the capability-free boundary (validate_oci_workspace_as_effective_identity) is unchanged. Signed-off-by: Matthew Grossman * chore(sandbox): remove unused capability dependency and refresh Landlock comments Signed-off-by: Matthew Grossman --------- Signed-off-by: Matthew Grossman --- Cargo.lock | 66 +- crates/openshell-sandbox/Cargo.toml | 1 - .../openshell-sandbox/src/boundary_server.rs | 10 +- crates/openshell-sandbox/src/identity.rs | 833 ------ crates/openshell-sandbox/src/lib.rs | 2 - crates/openshell-sandbox/src/main.rs | 73 - crates/openshell-sandbox/src/process.rs | 2457 +---------------- .../src/sandbox/linux/landlock.rs | 20 +- .../src/sandbox/linux/mod.rs | 9 +- 9 files changed, 118 insertions(+), 3353 deletions(-) delete mode 100644 crates/openshell-sandbox/src/identity.rs diff --git a/Cargo.lock b/Cargo.lock index b5e21ec266..c6173fee09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -770,12 +770,6 @@ dependencies = [ "sha2 0.11.0", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.13.2" @@ -923,17 +917,6 @@ dependencies = [ "libbz2-rs-sys", ] -[[package]] -name = "capctl" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a6e71767585f51c2a33fed6d67147ec0343725fc3c03bf4b89fe67fede56aa5" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", -] - [[package]] name = "cassowary" version = "0.3.0" @@ -1299,7 +1282,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.13.2", + "bitflags", "crossterm_winapi", "libc", "mio 0.8.11", @@ -1315,7 +1298,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.13.2", + "bitflags", "crossterm_winapi", "mio 1.2.0", "parking_lot", @@ -2912,7 +2895,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "533e68a5842e734946fe159fb03fc9bbbb254f590dd0d8ad321ae5ff7beca2c1" dependencies = [ - "bitflags 2.13.2", + "bitflags", "inotify-sys", "libc", ] @@ -3247,7 +3230,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" dependencies = [ - "bitflags 2.13.2", + "bitflags", "libc", ] @@ -3421,7 +3404,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "bitflags 2.13.2", + "bitflags", "libc", "plain", "redox_syscall 0.7.4", @@ -3700,7 +3683,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.13.2", + "bitflags", "cfg-if", "cfg_aliases", "libc", @@ -3712,7 +3695,7 @@ version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags 2.13.2", + "bitflags", "cfg-if", "cfg_aliases", "libc", @@ -3734,7 +3717,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.13.2", + "bitflags", "fsevent-sys", "inotify", "kqueue", @@ -3752,7 +3735,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "bitflags 2.13.2", + "bitflags", ] [[package]] @@ -4553,7 +4536,6 @@ dependencies = [ "async-trait", "base64 0.22.1", "bytes", - "capctl", "clap", "hex", "ipnet", @@ -5675,7 +5657,7 @@ version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" dependencies = [ - "bitflags 2.13.2", + "bitflags", "memchr", "unicase", ] @@ -5882,7 +5864,7 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" dependencies = [ - "bitflags 2.13.2", + "bitflags", "cassowary", "compact_str", "crossterm 0.27.0", @@ -5902,7 +5884,7 @@ version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags 2.13.2", + "bitflags", ] [[package]] @@ -5925,7 +5907,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.2", + "bitflags", ] [[package]] @@ -5934,7 +5916,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" dependencies = [ - "bitflags 2.13.2", + "bitflags", ] [[package]] @@ -6167,7 +6149,7 @@ checksum = "da7c230e0ed9cbeb92fbad6c8848985d6df2a1464c0dc247a021abd666e9005e" dependencies = [ "aes", "aws-lc-rs", - "bitflags 2.13.2", + "bitflags", "block-padding", "byteorder", "bytes", @@ -6249,7 +6231,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "093197e526668d92bba562e2bbbe98d1af9831bf080b619c736316ca1fa35101" dependencies = [ - "bitflags 2.13.2", + "bitflags", "bytes", "chrono", "dashmap", @@ -6317,7 +6299,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.13.2", + "bitflags", "errno", "libc", "linux-raw-sys 0.4.15", @@ -6330,7 +6312,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.13.2", + "bitflags", "errno", "libc", "linux-raw-sys 0.12.1", @@ -6557,7 +6539,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.2", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -7088,7 +7070,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27" dependencies = [ - "bitflags 2.13.2", + "bitflags", "byteorder", "bytes", "crc", @@ -7116,7 +7098,7 @@ checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.13.2", + "bitflags", "byteorder", "crc", "dotenvy", @@ -7812,7 +7794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "base64 0.22.1", - "bitflags 2.13.2", + "bitflags", "bytes", "futures-util", "http 1.4.0", @@ -8327,7 +8309,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.13.2", + "bitflags", "hashbrown 0.15.5", "indexmap", "semver", @@ -8911,7 +8893,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.13.2", + "bitflags", "indexmap", "log", "serde", diff --git a/crates/openshell-sandbox/Cargo.toml b/crates/openshell-sandbox/Cargo.toml index da8ddd45aa..50d91f0abf 100644 --- a/crates/openshell-sandbox/Cargo.toml +++ b/crates/openshell-sandbox/Cargo.toml @@ -73,7 +73,6 @@ russh-sftp = "3.0" uuid = { workspace = true } [target.'cfg(target_os = "linux")'.dependencies] -capctl = "0.2.4" landlock = "0.4" seccompiler = "0.5" socket2 = { workspace = true } diff --git a/crates/openshell-sandbox/src/boundary_server.rs b/crates/openshell-sandbox/src/boundary_server.rs index 6e7dd23ca3..f84f98d424 100644 --- a/crates/openshell-sandbox/src/boundary_server.rs +++ b/crates/openshell-sandbox/src/boundary_server.rs @@ -27,7 +27,6 @@ mod linux { use crate::boundary_io::BoundaryRuntimeState; use crate::delegated::{AgentSignaler, spawn_workload}; - use crate::identity::{DriverIdentity, resolve_process_identity}; use crate::main_session::{MainOutput, MainSession}; use crate::network_broker::NetworkBroker; use crate::process::ProcessStatus; @@ -2498,13 +2497,8 @@ mod linux { .build() ); } - let driver_identity = DriverIdentity::Resolved { - uid: self.config.workload_identity.uid, - gid: self.config.workload_identity.gid, - }; - if let Err(error) = resolve_process_identity(&mut policy, &driver_identity) { - return guest_error(BoundaryErrorKind::Process, error.to_string()); - } + policy.process.run_as_user = Some(self.config.workload_identity.uid.to_string()); + policy.process.run_as_group = Some(self.config.workload_identity.gid.to_string()); let launch = ManagedProcessLaunch { process_id: format!("{}:main:0", self.config.generation), spec, diff --git a/crates/openshell-sandbox/src/identity.rs b/crates/openshell-sandbox/src/identity.rs deleted file mode 100644 index df79a4137d..0000000000 --- a/crates/openshell-sandbox/src/identity.rs +++ /dev/null @@ -1,833 +0,0 @@ -// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -//! Driver identity normalization and OCI `USER` resolution. - -use crate::process::ResolvedProcessIdentity; -use miette::{IntoDiagnostic, Result}; -use openshell_core::policy::SandboxPolicy; -use std::fs::{File, OpenOptions}; -use std::io::Read; -use std::os::unix::fs::OpenOptionsExt; -use std::path::Path; - -const PASSWD_PATH: &str = "/etc/passwd"; -const GROUP_PATH: &str = "/etc/group"; -const MAX_ACCOUNT_FILE_SIZE: u64 = 1024 * 1024; -const MAX_ACCOUNT_LINE_SIZE: usize = 8 * 1024; -const MAX_ACCOUNT_FIELD_SIZE: usize = 1024; - -/// Identity input selected by the active compute driver. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum DriverIdentity { - /// Platform-selected identity used by Kubernetes and `OpenShift`. - Resolved { uid: u32, gid: u32 }, - /// Raw OCI `Config.User` selected by Docker and Podman. - OciUser { declaration: String }, - /// Drivers with no authoritative identity metadata. - None, -} - -impl DriverIdentity { - /// Normalize the protected driver environment into one identity variant. - pub fn from_env() -> Result { - let oci_user = optional_utf8_env(openshell_core::sandbox_env::OCI_IMAGE_USER)?; - let uid = optional_nonempty_utf8_env(openshell_core::sandbox_env::SANDBOX_UID)?; - let gid = optional_nonempty_utf8_env(openshell_core::sandbox_env::SANDBOX_GID)?; - Self::from_values(oci_user, uid, gid) - } - - fn from_values( - oci_user: Option, - uid: Option, - gid: Option, - ) -> Result { - // Resolved-identity drivers explicitly clear the OCI declaration so - // an image-baked or user-supplied value cannot select the OCI path. - // Preserve an empty declaration when no resolved pair is present: - // Docker and Podman use that state to reject images without USER. - let oci_user = if oci_user.as_deref() == Some("") && (uid.is_some() || gid.is_some()) { - None - } else { - oci_user - }; - - match (oci_user, uid, gid) { - (Some(declaration), None, None) => Ok(Self::OciUser { declaration }), - (None, Some(uid), Some(gid)) => { - let uid = uid.parse::().ok().filter(|uid| { - (openshell_policy::MIN_SANDBOX_UID..=openshell_policy::MAX_SANDBOX_UID) - .contains(uid) - }); - let gid = gid.parse::().ok().filter(|gid| { - (openshell_policy::MIN_SANDBOX_UID..=openshell_policy::MAX_SANDBOX_UID) - .contains(gid) - }); - let (Some(uid), Some(gid)) = (uid, gid) else { - return Err(miette::miette!( - "driver UID/GID must be numeric identities in range [{}, {}]", - openshell_policy::MIN_SANDBOX_UID, - openshell_policy::MAX_SANDBOX_UID - )); - }; - Ok(Self::Resolved { uid, gid }) - } - (None, None, None) => Ok(Self::None), - (Some(_), _, _) => Err(miette::miette!( - "{} conflicts with non-empty {}/{} driver identity", - openshell_core::sandbox_env::OCI_IMAGE_USER, - openshell_core::sandbox_env::SANDBOX_UID, - openshell_core::sandbox_env::SANDBOX_GID - )), - (None, _, _) => Err(miette::miette!( - "{} and {} must be supplied together", - openshell_core::sandbox_env::SANDBOX_UID, - openshell_core::sandbox_env::SANDBOX_GID - )), - } - } -} - -/// Apply a driver identity before any workload child becomes reachable. -pub fn resolve_process_identity( - policy: &mut SandboxPolicy, - driver_identity: &DriverIdentity, -) -> Result { - match driver_identity { - DriverIdentity::Resolved { uid, gid } => { - policy.process.run_as_user = Some(uid.to_string()); - policy.process.run_as_group = Some(gid.to_string()); - // Kubernetes/OpenShift already supply numeric policy values and - // retain their existing privilege-drop path. - Ok(ResolvedProcessIdentity::default()) - } - DriverIdentity::OciUser { declaration } => resolve_oci_process_identity_at( - policy, - declaration, - Path::new(PASSWD_PATH), - Path::new(GROUP_PATH), - ), - DriverIdentity::None => { - // VM/offline drivers retain the pre-OCI per-field fallback. A - // partial policy must never leave the omitted component at the - // root supervisor identity. - if policy - .process - .run_as_user - .as_deref() - .is_none_or(str::is_empty) - { - policy.process.run_as_user = Some("sandbox".into()); - } - if policy - .process - .run_as_group - .as_deref() - .is_none_or(str::is_empty) - { - policy.process.run_as_group = Some("sandbox".into()); - } - Ok(ResolvedProcessIdentity::default()) - } - } -} - -#[allow(clippy::similar_names)] -fn resolve_oci_process_identity_at( - policy: &mut SandboxPolicy, - declaration: &str, - passwd_path: &Path, - group_path: &Path, -) -> Result { - let explicit_user = policy - .process - .run_as_user - .as_deref() - .is_some_and(|value| !value.is_empty()); - let explicit_group = policy - .process - .run_as_group - .as_deref() - .is_some_and(|value| !value.is_empty()); - - if explicit_user && explicit_group { - return Ok(ResolvedProcessIdentity::default()); - } - - let (oci_user, oci_group) = split_oci_declaration(declaration); - let needs_primary_gid = !explicit_group && oci_group.is_none(); - let resolved_user = if !explicit_user || needs_primary_gid { - Some(resolve_required_oci_user( - oci_user, - passwd_path, - declaration, - needs_primary_gid, - )?) - } else { - None - }; - - let oci_uid = if explicit_user { - None - } else { - Some( - resolved_user - .as_ref() - .expect("omitted OCI user must have been resolved") - .0, - ) - }; - - if !explicit_user { - policy.process.run_as_user = Some(oci_user.to_string()); - } - - let oci_gid = if explicit_group { - None - } else { - let (group_value, gid) = match oci_group { - Some(group) if !group.is_empty() => { - let gid = validate_oci_group(group, group_path, declaration)?; - (group.to_string(), gid) - } - Some(_) => { - return Err(miette::miette!( - "OCI USER '{declaration}' has an empty group component" - )); - } - None => { - let gid = resolved_user - .and_then(|(_, primary_gid)| primary_gid) - .ok_or_else(|| { - miette::miette!( - "OCI USER '{declaration}' uses a numeric UID without an explicit group, \ - but /etc/passwd has no matching primary GID" - ) - })?; - (gid.to_string(), gid) - } - }; - policy.process.run_as_group = Some(group_value); - Some(gid) - }; - - Ok(ResolvedProcessIdentity::new(oci_uid, oci_gid)) -} - -fn split_oci_declaration(declaration: &str) -> (&str, Option<&str>) { - declaration - .split_once(':') - .map_or((declaration, None), |(user, group)| (user, Some(group))) -} - -fn resolve_required_oci_user( - user: &str, - passwd_path: &Path, - declaration: &str, - require_primary_gid: bool, -) -> Result<(u32, Option)> { - if user.is_empty() { - return Err(miette::miette!( - "OCI USER is required because run_as_user is omitted" - )); - } - validate_component(user, "OCI user")?; - if user == "root" { - return Err(miette::miette!("OCI USER '{declaration}' selects root")); - } - if let Ok(uid) = user.parse::() { - if uid == 0 { - return Err(miette::miette!("OCI USER '{declaration}' selects UID 0")); - } - let primary_gid = if require_primary_gid { - find_passwd_by_uid(passwd_path, uid)?.map(|entry| entry.gid) - } else { - None - }; - if primary_gid == Some(0) { - return Err(miette::miette!( - "OCI USER '{declaration}' resolves to prohibited primary GID 0" - )); - } - return Ok((uid, primary_gid)); - } - let entry = find_passwd_by_name(passwd_path, user)? - .ok_or_else(|| miette::miette!("OCI USER name '{user}' was not found in /etc/passwd"))?; - if entry.uid == 0 { - return Err(miette::miette!( - "OCI USER '{declaration}' resolves to prohibited UID 0" - )); - } - if require_primary_gid && entry.gid == 0 { - return Err(miette::miette!( - "OCI USER '{declaration}' resolves to prohibited primary GID 0" - )); - } - Ok((entry.uid, require_primary_gid.then_some(entry.gid))) -} - -fn validate_oci_group(value: &str, group_path: &Path, declaration: &str) -> Result { - validate_component(value, "OCI group")?; - if value == "root" { - return Err(miette::miette!( - "OCI USER '{declaration}' selects root group" - )); - } - let gid = if let Ok(gid) = value.parse::() { - gid - } else { - find_group_by_name(group_path, value)? - .ok_or_else(|| miette::miette!("OCI group '{value}' was not found in /etc/group"))? - .gid - }; - if gid == 0 { - return Err(miette::miette!( - "OCI USER '{declaration}' resolves to prohibited GID 0" - )); - } - Ok(gid) -} - -fn validate_component(value: &str, kind: &str) -> Result<()> { - if value.is_empty() - || value.len() > MAX_ACCOUNT_FIELD_SIZE - || value.trim() != value - || value.chars().any(|ch| ch.is_control() || ch == ':') - { - return Err(miette::miette!("{kind} component '{value}' is malformed")); - } - Ok(()) -} - -#[derive(Debug, Clone, PartialEq, Eq)] -struct PasswdEntry { - uid: u32, - gid: u32, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -struct GroupEntry { - gid: u32, -} - -fn find_passwd_by_name(path: &Path, name: &str) -> Result> { - find_unique(path, |fields| { - (fields.first().copied() == Some(name)).then(|| parse_passwd(fields)) - }) -} - -fn find_passwd_by_uid(path: &Path, uid: u32) -> Result> { - find_unique(path, |fields| { - fields - .get(2) - .and_then(|value| value.parse::().ok()) - .filter(|candidate| *candidate == uid) - .map(|_| parse_passwd(fields)) - }) -} - -fn find_group_by_name(path: &Path, name: &str) -> Result> { - find_unique(path, |fields| { - (fields.first().copied() == Some(name)).then(|| parse_group(fields)) - }) -} - -/// Resolve supplementary groups declared for an OCI named user without -/// consulting NSS. Numeric OCI users have no trustworthy group-membership -/// name and therefore receive no supplementary groups. -pub fn resolve_oci_supplementary_gids(declaration: &str, primary_gid: u32) -> Result> { - resolve_oci_supplementary_gids_at(declaration, primary_gid, Path::new(GROUP_PATH)) -} - -fn resolve_oci_supplementary_gids_at( - declaration: &str, - primary_gid: u32, - group_path: &Path, -) -> Result> { - let (user, _) = split_oci_declaration(declaration); - validate_component(user, "OCI user")?; - if user.parse::().is_ok() { - return Ok(Vec::new()); - } - - let content = read_account_file(group_path)?; - let mut gids = vec![primary_gid]; - for line in content.lines() { - if line.is_empty() || line.starts_with('#') { - continue; - } - if line.len() > MAX_ACCOUNT_LINE_SIZE { - return Err(miette::miette!( - "account file '{}' contains an oversized line", - group_path.display() - )); - } - let fields = line.split(':').collect::>(); - if fields.len() != 4 - || fields - .iter() - .any(|field| field.len() > MAX_ACCOUNT_FIELD_SIZE) - { - return Err(miette::miette!( - "group membership entry in '{}' is malformed", - group_path.display() - )); - } - if !fields[3].split(',').any(|member| member == user) { - continue; - } - let gid = fields[2].parse::().map_err(|_| { - miette::miette!( - "group membership GID in '{}' is malformed", - group_path.display() - ) - })?; - if gid == 0 { - return Err(miette::miette!( - "OCI user '{user}' is a member of prohibited GID 0" - )); - } - gids.push(gid); - } - gids.sort_unstable(); - gids.dedup(); - Ok(gids) -} - -fn find_unique( - path: &Path, - mut select: impl FnMut(&[&str]) -> Option>, -) -> Result> { - let content = read_account_file(path)?; - let mut found = None; - for line in content.lines() { - if line.is_empty() || line.starts_with('#') { - continue; - } - if line.len() > MAX_ACCOUNT_LINE_SIZE { - return Err(miette::miette!( - "account file '{}' contains an oversized line", - path.display() - )); - } - let fields = line.split(':').collect::>(); - if fields - .iter() - .any(|field| field.len() > MAX_ACCOUNT_FIELD_SIZE) - { - return Err(miette::miette!( - "account file '{}' contains an oversized field", - path.display() - )); - } - let Some(candidate) = select(&fields) else { - continue; - }; - let candidate = candidate?; - if found.replace(candidate).is_some() { - return Err(miette::miette!( - "account identity is ambiguous in '{}'", - path.display() - )); - } - } - Ok(found) -} - -fn parse_passwd(fields: &[&str]) -> Result { - if fields.len() != 7 { - return Err(miette::miette!("matching /etc/passwd entry is malformed")); - } - Ok(PasswdEntry { - uid: fields[2] - .parse() - .map_err(|_| miette::miette!("matching /etc/passwd UID is malformed"))?, - gid: fields[3] - .parse() - .map_err(|_| miette::miette!("matching /etc/passwd GID is malformed"))?, - }) -} - -fn parse_group(fields: &[&str]) -> Result { - if fields.len() != 4 { - return Err(miette::miette!("matching /etc/group entry is malformed")); - } - Ok(GroupEntry { - gid: fields[2] - .parse() - .map_err(|_| miette::miette!("matching /etc/group GID is malformed"))?, - }) -} - -fn read_account_file(path: &Path) -> Result { - let mut options = OpenOptions::new(); - options - .read(true) - .custom_flags(libc::O_CLOEXEC | libc::O_NOFOLLOW); - let mut file = options - .open(path) - .into_diagnostic() - .map_err(|error| miette::miette!("failed to open '{}': {error}", path.display()))?; - validate_account_file(&file, path)?; - - let mut bytes = Vec::new(); - file.by_ref() - .take(MAX_ACCOUNT_FILE_SIZE + 1) - .read_to_end(&mut bytes) - .into_diagnostic()?; - if bytes.len() as u64 > MAX_ACCOUNT_FILE_SIZE { - return Err(miette::miette!( - "account file '{}' exceeds {MAX_ACCOUNT_FILE_SIZE} bytes", - path.display() - )); - } - String::from_utf8(bytes) - .map_err(|_| miette::miette!("account file '{}' is not valid UTF-8", path.display())) -} - -fn validate_account_file(file: &File, path: &Path) -> Result<()> { - let metadata = file.metadata().into_diagnostic()?; - if !metadata.is_file() { - return Err(miette::miette!( - "account path '{}' is not a regular file", - path.display() - )); - } - if metadata.len() > MAX_ACCOUNT_FILE_SIZE { - return Err(miette::miette!( - "account file '{}' exceeds {MAX_ACCOUNT_FILE_SIZE} bytes", - path.display() - )); - } - Ok(()) -} - -fn optional_utf8_env(name: &str) -> Result> { - std::env::var_os(name) - .map(|value| { - value - .into_string() - .map_err(|_| miette::miette!("{name} is not valid UTF-8")) - }) - .transpose() -} - -fn optional_nonempty_utf8_env(name: &str) -> Result> { - Ok(optional_utf8_env(name)?.filter(|value| !value.is_empty())) -} - -#[cfg(test)] -mod tests { - use super::*; - use openshell_core::policy::SandboxPolicy; - use std::fs; - use tempfile::tempdir; - - fn account_files( - passwd: &str, - group: &str, - ) -> (tempfile::TempDir, std::path::PathBuf, std::path::PathBuf) { - let dir = tempdir().unwrap(); - let passwd_path = dir.path().join("passwd"); - let group_path = dir.path().join("group"); - fs::write(&passwd_path, passwd).unwrap(); - fs::write(&group_path, group).unwrap(); - (dir, passwd_path, group_path) - } - - fn policy(user: Option<&str>, group: Option<&str>) -> SandboxPolicy { - let mut policy = SandboxPolicy { - version: 1, - filesystem: openshell_core::policy::FilesystemPolicy::default(), - network: openshell_core::policy::NetworkPolicy::default(), - landlock: openshell_core::policy::LandlockPolicy::default(), - process: openshell_core::policy::ProcessPolicy::default(), - }; - policy.process.run_as_user = user.map(str::to_string); - policy.process.run_as_group = group.map(str::to_string); - policy - } - - #[test] - fn per_field_policy_precedence_resolves_complete_pair() { - let (_dir, passwd, group) = account_files( - "app:x:1234:1235::/home/app:/bin/sh\nsandbox:x:2000:2001::/sandbox:/bin/sh\n", - "staff:x:1235:\nsandbox:x:2001:\n", - ); - let cases = [ - ( - Some("2000"), - Some("2001"), - "root", - "2000", - "2001", - None, - None, - ), - ( - Some("2000"), - None, - "app:staff", - "2000", - "staff", - None, - Some(1235), - ), - ( - None, - Some("2001"), - "app:root", - "app", - "2001", - Some(1234), - None, - ), - ( - None, - None, - "app:staff", - "app", - "staff", - Some(1234), - Some(1235), - ), - (None, None, "app", "app", "1235", Some(1234), Some(1235)), - ]; - for ( - user, - group_name, - declaration, - expected_user, - expected_group, - resolved_uid, - resolved_gid, - ) in cases - { - let mut policy = policy(user, group_name); - let resolved = - resolve_oci_process_identity_at(&mut policy, declaration, &passwd, &group).unwrap(); - assert_eq!(policy.process.run_as_user.as_deref(), Some(expected_user)); - assert_eq!(policy.process.run_as_group.as_deref(), Some(expected_group)); - assert_eq!(resolved.uid(), resolved_uid); - assert_eq!(resolved.gid(), resolved_gid); - } - } - - #[test] - fn numeric_pair_does_not_require_account_entries() { - let dir = tempdir().unwrap(); - let passwd = dir.path().join("missing-passwd"); - let group = dir.path().join("missing-group"); - let mut policy = policy(None, None); - let resolved = - resolve_oci_process_identity_at(&mut policy, "1234:1235", &passwd, &group).unwrap(); - assert_eq!(policy.process.run_as_user.as_deref(), Some("1234")); - assert_eq!(policy.process.run_as_group.as_deref(), Some("1235")); - assert_eq!( - resolved, - ResolvedProcessIdentity::new(Some(1234), Some(1235)) - ); - } - - #[test] - fn explicit_identity_is_preserved_without_inspecting_oci_or_accounts() { - let dir = tempdir().unwrap(); - let mut policy = policy(Some("sandbox"), Some("sandbox")); - - let resolved = resolve_oci_process_identity_at( - &mut policy, - "root:root", - &dir.path().join("missing-passwd"), - &dir.path().join("missing-group"), - ) - .unwrap(); - - assert_eq!(policy.process.run_as_user.as_deref(), Some("sandbox")); - assert_eq!(policy.process.run_as_group.as_deref(), Some("sandbox")); - assert_eq!(resolved, ResolvedProcessIdentity::default()); - } - - #[test] - fn driver_identity_inputs_are_mutually_exclusive_and_complete() { - assert_eq!( - DriverIdentity::from_values(Some("app".into()), None, None).unwrap(), - DriverIdentity::OciUser { - declaration: "app".into() - } - ); - assert_eq!( - DriverIdentity::from_values(None, Some("1234".into()), Some("1235".into())).unwrap(), - DriverIdentity::Resolved { - uid: 1234, - gid: 1235 - } - ); - assert_eq!( - DriverIdentity::from_values(None, Some("500".into()), Some("30".into())).unwrap(), - DriverIdentity::Resolved { uid: 500, gid: 30 } - ); - assert_eq!( - DriverIdentity::from_values( - Some(String::new()), - Some("1234".into()), - Some("1235".into()) - ) - .unwrap(), - DriverIdentity::Resolved { - uid: 1234, - gid: 1235 - } - ); - assert_eq!( - DriverIdentity::from_values(Some(String::new()), None, None).unwrap(), - DriverIdentity::OciUser { - declaration: String::new() - } - ); - assert_eq!( - DriverIdentity::from_values(None, None, None).unwrap(), - DriverIdentity::None - ); - assert!( - DriverIdentity::from_values( - Some("app".into()), - Some("1234".into()), - Some("1235".into()) - ) - .is_err() - ); - assert!(DriverIdentity::from_values(None, Some("1234".into()), None).is_err()); - } - - #[test] - fn no_driver_identity_completes_partial_policy_with_sandbox() { - let cases = [ - (None, Some("staff"), "sandbox", "staff"), - (Some("app"), None, "app", "sandbox"), - (None, None, "sandbox", "sandbox"), - (Some("app"), Some("staff"), "app", "staff"), - ]; - - for (user, group, expected_user, expected_group) in cases { - let mut policy = policy(user, group); - let resolved = resolve_process_identity(&mut policy, &DriverIdentity::None).unwrap(); - - assert_eq!(policy.process.run_as_user.as_deref(), Some(expected_user)); - assert_eq!(policy.process.run_as_group.as_deref(), Some(expected_group)); - assert_eq!(resolved, ResolvedProcessIdentity::default()); - } - } - - #[test] - fn numeric_uid_uses_passwd_primary_gid() { - let (_dir, passwd, group) = account_files("app:x:1234:4321::/home/app:/bin/sh\n", ""); - let mut policy = policy(None, None); - let resolved = - resolve_oci_process_identity_at(&mut policy, "1234", &passwd, &group).unwrap(); - assert_eq!(policy.process.run_as_group.as_deref(), Some("4321")); - assert_eq!( - resolved, - ResolvedProcessIdentity::new(Some(1234), Some(4321)) - ); - } - - #[test] - fn named_oci_user_resolves_bounded_supplementary_groups() { - let (_dir, _passwd, group) = account_files( - "", - "primary:x:1235:\nvideo:x:44:app,other\naudio:x:63:other\nrender:x:107:app\n", - ); - - let gids = resolve_oci_supplementary_gids_at("app:primary", 1235, &group).unwrap(); - assert_eq!(gids, vec![44, 107, 1235]); - } - - #[test] - fn numeric_oci_user_has_no_named_supplementary_groups() { - let dir = tempdir().unwrap(); - let missing_group = dir.path().join("missing-group"); - - let gids = resolve_oci_supplementary_gids_at("1234:1235", 1235, &missing_group).unwrap(); - assert!(gids.is_empty()); - } - - #[test] - fn oci_supplementary_membership_rejects_root_group() { - let (_dir, _passwd, group) = account_files("", "root:x:0:app\n"); - - let error = - resolve_oci_supplementary_gids_at("app", 1235, &group).expect_err("GID 0 must fail"); - assert!(error.to_string().contains("prohibited GID 0")); - } - - #[test] - fn missing_unknown_ambiguous_and_root_identities_fail() { - let (_dir, passwd, group) = account_files( - "app:x:1234:1235::/home/app:/bin/sh\napp:x:2234:2235::/home/app2:/bin/sh\n", - "staff:x:1235:\nstaff:x:2235:\n", - ); - for declaration in ["", "unknown", "app", "9999", "0:1235", "1234:0"] { - let mut policy = policy(None, None); - assert!( - resolve_oci_process_identity_at(&mut policy, declaration, &passwd, &group).is_err(), - "{declaration:?} unexpectedly resolved" - ); - } - } - - #[test] - fn selected_component_is_validated_independently() { - let (_dir, passwd, group) = - account_files("app:x:1234:1235::/home/app:/bin/sh\n", "staff:x:1235:\n"); - - let mut explicit_user = policy(Some("1234"), None); - let resolved = - resolve_oci_process_identity_at(&mut explicit_user, "root:staff", &passwd, &group) - .unwrap(); - assert_eq!(explicit_user.process.run_as_user.as_deref(), Some("1234")); - assert_eq!(explicit_user.process.run_as_group.as_deref(), Some("staff")); - assert_eq!(resolved, ResolvedProcessIdentity::new(None, Some(1235))); - - let mut explicit_group = policy(None, Some("1235")); - let resolved = - resolve_oci_process_identity_at(&mut explicit_group, "app:root", &passwd, &group) - .unwrap(); - assert_eq!(explicit_group.process.run_as_user.as_deref(), Some("app")); - assert_eq!(explicit_group.process.run_as_group.as_deref(), Some("1235")); - assert_eq!(resolved, ResolvedProcessIdentity::new(Some(1234), None)); - } - - #[test] - fn named_oci_components_mapping_to_root_are_rejected() { - let (_dir, passwd, group) = account_files( - "root_alias:x:0:1235::/root:/bin/sh\napp:x:1234:1235::/home/app:/bin/sh\n", - "root_alias:x:0:\nstaff:x:1235:\n", - ); - - let mut root_user = policy(None, None); - assert!( - resolve_oci_process_identity_at(&mut root_user, "root_alias:staff", &passwd, &group) - .is_err() - ); - - let mut root_group = policy(None, None); - assert!( - resolve_oci_process_identity_at(&mut root_group, "app:root_alias", &passwd, &group) - .is_err() - ); - } - - #[cfg(unix)] - #[test] - fn account_file_symlinks_are_rejected() { - use std::os::unix::fs::symlink; - - let (_dir, passwd, group) = - account_files("app:x:1234:1235::/home/app:/bin/sh\n", "staff:x:1235:\n"); - let link = passwd.with_file_name("passwd-link"); - symlink(&passwd, &link).unwrap(); - - let mut policy = policy(None, None); - assert!(resolve_oci_process_identity_at(&mut policy, "app:staff", &link, &group).is_err()); - } -} diff --git a/crates/openshell-sandbox/src/lib.rs b/crates/openshell-sandbox/src/lib.rs index 5ba996181a..6c3a9829f9 100644 --- a/crates/openshell-sandbox/src/lib.rs +++ b/crates/openshell-sandbox/src/lib.rs @@ -11,8 +11,6 @@ mod boundary_server; pub mod child_env; #[cfg(target_os = "linux")] pub(crate) mod delegated; -#[cfg(unix)] -pub mod identity; #[cfg(target_os = "linux")] pub mod main_session; pub mod managed_children; diff --git a/crates/openshell-sandbox/src/main.rs b/crates/openshell-sandbox/src/main.rs index 4dad330771..8ef97be1a3 100644 --- a/crates/openshell-sandbox/src/main.rs +++ b/crates/openshell-sandbox/src/main.rs @@ -33,7 +33,6 @@ const SANDBOX_RUNTIME_ROOT: &str = "/.openshell/runtime"; #[cfg(target_os = "linux")] const SANDBOX_STATE_ROOT: &str = "/.openshell/state"; -const VALIDATE_WORKSPACE_SUBCOMMAND: &str = "validate-workspace"; const CAPABILITY_PROBE_SUBCOMMAND: &str = "capability-probe"; const CAPABILITY_PROBE_LAUNCH_SUBCOMMAND: &str = "capability-probe-launch"; const CAPABILITY_SOCKET_CHILD_SUBCOMMAND: &str = "capability-socket-child"; @@ -60,50 +59,6 @@ struct BoundaryArgs { log_level: String, } -/// Internal one-shot command used by trusted driver bootstrap to validate an -/// image-provided workdir as the final sandbox identity. -#[derive(Parser, Debug)] -#[command(name = "validate-workspace", hide = true)] -struct ValidateWorkspaceArgs { - #[arg(long)] - workdir: String, - #[arg(long)] - expected_uid: u32, - #[arg(long)] - expected_gid: u32, -} - -#[cfg(target_os = "linux")] -fn validate_workspace(args: &[String]) -> Result<()> { - let args = ValidateWorkspaceArgs::try_parse_from( - std::iter::once(VALIDATE_WORKSPACE_SUBCOMMAND.to_string()).chain(args.iter().cloned()), - ) - .into_diagnostic()?; - let actual = ( - nix::unistd::geteuid().as_raw(), - nix::unistd::getegid().as_raw(), - ); - if actual != (args.expected_uid, args.expected_gid) { - return Err(miette::miette!( - "workspace validator privilege drop failed: expected {}:{}, got {}:{}", - args.expected_uid, - args.expected_gid, - actual.0, - actual.1 - )); - } - openshell_sandbox::process::validate_oci_workspace_as_effective_identity(Path::new( - &args.workdir, - )) -} - -#[cfg(not(target_os = "linux"))] -fn validate_workspace(_args: &[String]) -> Result<()> { - Err(miette::miette!( - "workspace validation is only supported on Unix" - )) -} - /// Run the active Phase 0 probe inside the exact workload runtime profile. #[cfg(target_os = "linux")] #[allow(unsafe_code)] @@ -1923,9 +1878,6 @@ fn main() -> Result<()> { } return seed_kubernetes_workspace(); } - if raw_args.get(1).map(String::as_str) == Some(VALIDATE_WORKSPACE_SUBCOMMAND) { - return validate_workspace(&raw_args[2..]); - } if raw_args.get(1).map(String::as_str) == Some(CAPABILITY_PROBE_SUBCOMMAND) { return run_capability_probe(); } @@ -2031,31 +1983,6 @@ mod tests { assert!(destination.join(".openshell-initialized").is_file()); } - #[cfg(target_os = "linux")] - #[test] - fn workspace_validation_subcommand_uses_final_policy_identity() { - let uid = nix::unistd::geteuid().as_raw(); - let gid = nix::unistd::getegid().as_raw(); - if uid < 1000 || gid < 1000 { - return; - } - let dir = tempfile::tempdir_in("/tmp").unwrap(); - std::fs::set_permissions(dir.path(), std::fs::Permissions::from_mode(0o711)).unwrap(); - let root = dir.path().canonicalize().unwrap().join("workspace"); - std::fs::create_dir(&root).unwrap(); - std::fs::set_permissions(&root, std::fs::Permissions::from_mode(0o700)).unwrap(); - let args = vec![ - "--workdir".to_string(), - root.display().to_string(), - "--expected-uid".to_string(), - uid.to_string(), - "--expected-gid".to_string(), - gid.to_string(), - ]; - - validate_workspace(&args).expect("current identity should retain workspace authority"); - } - /// Drives `copy_self`'s file-copy logic against an arbitrary source path /// so tests don't depend on `current_exe()`. fn copy_executable(src: &Path, dest: &Path) -> Result<()> { diff --git a/crates/openshell-sandbox/src/process.rs b/crates/openshell-sandbox/src/process.rs index 7b61d30030..595ceac29e 100644 --- a/crates/openshell-sandbox/src/process.rs +++ b/crates/openshell-sandbox/src/process.rs @@ -11,22 +11,19 @@ use crate::sandbox; use miette::WrapErr; use miette::{IntoDiagnostic, Result}; use nix::sys::signal::{self, Signal}; -use nix::unistd::{Gid, Group, Pid, Uid, User}; +use nix::unistd::{Pid, User}; use openshell_core::policy::SandboxPolicy; use std::collections::HashMap; -use std::ffi::CString; #[cfg(unix)] use std::os::fd::AsRawFd; -#[cfg(unix)] -use std::os::unix::fs::{MetadataExt, PermissionsExt}; -#[cfg(any(test, unix))] +#[cfg(target_os = "linux")] use std::path::Path; use std::path::PathBuf; use std::process::Stdio; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use tokio::process::{Child, ChildStderr, ChildStdin, ChildStdout, Command}; -use tracing::{debug, info}; +use tracing::debug; // `libc::TIOCSCTTY` and the request parameter accepted by `ioctl` vary across // glibc, musl, and BSD targets. The conversion is a no-op on some targets but @@ -40,45 +37,6 @@ fn set_controlling_tty(fd: libc::c_int) -> std::io::Result<()> { Ok(()) } -/// Numeric identity components resolved once from driver-owned metadata. -/// -/// A component is `None` when the corresponding policy field was explicit and -/// must continue through the existing policy identity path. OCI-derived -/// components are carried numerically so later filesystem setup and direct/SSH -/// privilege drops cannot resolve them differently through NSS. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -pub struct ResolvedProcessIdentity { - uid: Option, - gid: Option, -} - -impl ResolvedProcessIdentity { - #[must_use] - pub const fn new(uid: Option, gid: Option) -> Self { - Self { uid, gid } - } - - #[must_use] - pub const fn uid(self) -> Option { - self.uid - } - - #[must_use] - pub const fn gid(self) -> Option { - self.gid - } - - /// Whether at least one process identity component came from OCI `USER`. - /// - /// Platform-resolved identities are written directly into the policy and - /// return the default value, so this is specific to Docker/Podman OCI - /// fallback without adding another driver contract. - #[must_use] - pub const fn uses_oci_user_fallback(self) -> bool { - self.uid.is_some() || self.gid.is_some() - } -} - /// Resolved process workspace and its child-environment semantics. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct ResolvedWorkspace { @@ -380,46 +338,6 @@ fn parse_pids_max(contents: &str) -> RuntimePidLimitStatus { } } -#[cfg(target_os = "linux")] -fn drop_capability_bounding_set() -> Result<()> { - let clear_result = capctl::caps::bounding::clear(); - let remaining = capctl::caps::bounding::probe(); - - validate_capability_bounding_set_clear( - clear_result, - remaining, - capctl::caps::bounding::clear_unknown, - ) -} - -#[cfg(target_os = "linux")] -fn validate_capability_bounding_set_clear( - clear_result: capctl::Result<()>, - remaining: capctl::caps::CapSet, - clear_unknown: impl FnOnce() -> capctl::Result<()>, -) -> Result<()> { - match clear_result { - Ok(()) if remaining.is_empty() => Ok(()), - Ok(()) => Err(miette::miette!( - "Failed to clear child capability bounding set: capabilities remain raised: {remaining:?}" - )), - Err(err) if err.code() == libc::EPERM && remaining.is_empty() => match clear_unknown() { - Ok(()) => { - debug!( - "CAP_SETPCAP is unavailable, but the child capability bounding set is already empty" - ); - Ok(()) - } - Err(unknown_err) => Err(miette::miette!( - "Failed to clear unknown child capability bounding set entries: {unknown_err}" - )), - }, - Err(err) => Err(miette::miette!( - "Failed to clear child capability bounding set: {err}" - )), - } -} - #[cfg(target_os = "linux")] pub fn spawn_command_with_workload_launcher( launcher: &openshell_isolation_interface::linux::workload_launcher::WorkloadLauncher, @@ -920,513 +838,11 @@ impl Drop for ProcessHandle { } } -/// Validate the configured process user. -/// -/// Numeric identities do not require a passwd entry. The legacy explicit -/// `"sandbox"` identity and other names must resolve in `/etc/passwd`. -#[cfg(unix)] -pub fn validate_sandbox_user(policy: &SandboxPolicy) -> Result<()> { - let identity = policy.process.run_as_user.as_deref().unwrap_or("sandbox"); - - if let Ok(uid) = identity.parse::() { - if !(MIN_SANDBOX_UID..=MAX_SANDBOX_UID).contains(&uid) { - return Err(miette::miette!( - "process user UID must be in range [{MIN_SANDBOX_UID}, {MAX_SANDBOX_UID}]" - )); - } - openshell_ocsf::ocsf_emit!( - openshell_ocsf::ConfigStateChangeBuilder::new(openshell_ocsf::ctx::ctx()) - .severity(openshell_ocsf::SeverityId::Informational) - .status(openshell_ocsf::StatusId::Success) - .state(openshell_ocsf::StateId::Enabled, "validated") - .message(format!( - "Accepted numeric UID {identity} (no passwd entry required)" - )) - .build() - ); - return Ok(()); - } - - // Legacy explicit "sandbox" name — must exist in /etc/passwd. - if identity == "sandbox" { - match User::from_name("sandbox") { - Ok(Some(_)) => { - openshell_ocsf::ocsf_emit!( - openshell_ocsf::ConfigStateChangeBuilder::new(openshell_ocsf::ctx::ctx()) - .severity(openshell_ocsf::SeverityId::Informational) - .status(openshell_ocsf::StatusId::Success) - .state(openshell_ocsf::StateId::Enabled, "validated") - .message("Validated 'sandbox' user exists in image") - .build() - ); - } - Ok(None) => { - return Err(miette::miette!( - "explicit process user 'sandbox' was not found in the image" - )); - } - Err(e) => { - return Err(miette::miette!("failed to look up 'sandbox' user: {e}")); - } - } - } else if !identity.is_empty() { - // Other names are supported by local/offline policy paths and must - // resolve before privilege dropping. - match User::from_name(identity) { - Ok(Some(_)) => { - tracing::warn!(identity, "named process user accepted via passwd entry"); - } - Ok(None) => { - return Err(miette::miette!( - "unrecognized sandbox identity '{identity}'; \ - expected 'sandbox' or a numeric UID in range [{MIN_SANDBOX_UID}, {MAX_SANDBOX_UID}]" - )); - } - Err(e) => { - return Err(miette::miette!( - "failed to look up identity '{identity}': {e}" - )); - } - } - } - - Ok(()) -} - -/// Validate that the configured sandbox group identity is acceptable. -/// -/// Mirrors [`validate_sandbox_user`] for the group dimension. -#[cfg(unix)] -pub fn validate_sandbox_group(policy: &SandboxPolicy) -> Result<()> { - let identity = policy.process.run_as_group.as_deref().unwrap_or("sandbox"); - - if let Ok(gid) = identity.parse::() { - if !(MIN_SANDBOX_UID..=MAX_SANDBOX_UID).contains(&gid) { - return Err(miette::miette!( - "process group GID must be in range [{MIN_SANDBOX_UID}, {MAX_SANDBOX_UID}]" - )); - } - openshell_ocsf::ocsf_emit!( - openshell_ocsf::ConfigStateChangeBuilder::new(openshell_ocsf::ctx::ctx()) - .severity(openshell_ocsf::SeverityId::Informational) - .status(openshell_ocsf::StatusId::Success) - .state(openshell_ocsf::StateId::Enabled, "validated") - .message(format!( - "Accepted numeric GID {identity} (no group entry required)" - )) - .build() - ); - return Ok(()); - } - - if identity == "sandbox" { - match Group::from_name("sandbox") { - Ok(Some(_)) => { - openshell_ocsf::ocsf_emit!( - openshell_ocsf::ConfigStateChangeBuilder::new(openshell_ocsf::ctx::ctx()) - .severity(openshell_ocsf::SeverityId::Informational) - .status(openshell_ocsf::StatusId::Success) - .state(openshell_ocsf::StateId::Enabled, "validated") - .message("Validated 'sandbox' group exists in image") - .build() - ); - } - Ok(None) => { - return Err(miette::miette!( - "explicit process group 'sandbox' was not found in the image" - )); - } - Err(e) => { - return Err(miette::miette!("failed to look up 'sandbox' group: {e}")); - } - } - } else if !identity.is_empty() { - match Group::from_name(identity) { - Ok(Some(_)) => { - tracing::warn!(identity, "named process group accepted via group entry"); - } - Ok(None) => { - return Err(miette::miette!( - "unrecognized sandbox group identity '{identity}'; \ - expected 'sandbox' or a numeric GID in range [{MIN_SANDBOX_UID}, {MAX_SANDBOX_UID}]" - )); - } - Err(e) => { - return Err(miette::miette!( - "failed to look up group identity '{identity}': {e}" - )); - } - } - } - - Ok(()) -} - -#[cfg(unix)] -pub fn validate_sandbox_user_with_identity( - policy: &SandboxPolicy, - resolved_identity: ResolvedProcessIdentity, -) -> Result<()> { - let Some(uid) = resolved_identity.uid() else { - return validate_sandbox_user(policy); - }; - if uid == 0 { - return Err(miette::miette!("process user must not select UID 0")); - } - Ok(()) -} - -#[cfg(unix)] -pub fn validate_sandbox_group_with_identity( - policy: &SandboxPolicy, - resolved_identity: ResolvedProcessIdentity, -) -> Result<()> { - let Some(gid) = resolved_identity.gid() else { - return validate_sandbox_group(policy); - }; - if gid == 0 { - return Err(miette::miette!("process group must not select GID 0")); - } - Ok(()) -} - -pub use openshell_policy::{MAX_SANDBOX_UID, MIN_SANDBOX_UID}; - -/// Prepare a `read_write` path for the sandboxed process. -/// -/// Returns `true` when the path was created by the supervisor and therefore -/// still needs to be chowned to the sandbox user/group. Existing paths keep -/// their image-defined ownership. -#[cfg(unix)] -fn prepare_read_write_path(path: &Path) -> Result { - // SECURITY: use symlink_metadata (lstat) to inspect each path *before* - // calling chown. chown follows symlinks, so a malicious container image - // could place a symlink (e.g. /sandbox -> /etc/shadow) to trick the - // root supervisor into transferring ownership of arbitrary files. - // The TOCTOU window between lstat and chown is not exploitable because - // no untrusted process is running yet (the child has not been forked). - if let Ok(meta) = std::fs::symlink_metadata(path) { - if meta.file_type().is_symlink() { - return Err(miette::miette!( - "read_write path '{}' is a symlink — refusing to chown (potential privilege escalation)", - path.display() - )); - } - - debug!( - path = %path.display(), - "Preserving ownership for existing read_write path" - ); - Ok(false) - } else { - debug!(path = %path.display(), "Creating read_write directory"); - std::fs::create_dir_all(path).into_diagnostic()?; - Ok(true) - } -} - -/// Update `/etc/passwd` and `/etc/group` so the "sandbox" user/group entries -/// match the driver-injected UID/GID from environment variables. -/// -/// When `OPENSHELL_SANDBOX_UID` is set, the image-baked "sandbox" entry may -/// have a different UID. Updating the files ensures `whoami`, `id`, `ls -l`, -/// SSH sessions, and `initgroups` resolve the sandbox identity correctly. -/// If no "sandbox" entry exists, one is appended. -#[cfg(unix)] -pub fn update_sandbox_passwd_entries() -> Result<()> { - let uid_str = match std::env::var(openshell_core::sandbox_env::SANDBOX_UID) { - Ok(v) if !v.is_empty() => v, - _ => return Ok(()), - }; - let gid_str = match std::env::var(openshell_core::sandbox_env::SANDBOX_GID) { - Ok(v) if !v.is_empty() => v, - _ => uid_str.clone(), - }; - - let _: u32 = uid_str - .parse() - .map_err(|e| miette::miette!("invalid OPENSHELL_SANDBOX_UID '{uid_str}': {e}"))?; - let _: u32 = gid_str - .parse() - .map_err(|e| miette::miette!("invalid OPENSHELL_SANDBOX_GID '{gid_str}': {e}"))?; - - update_passwd_file(&uid_str, &gid_str)?; - update_group_file(&gid_str)?; - - info!( - uid = %uid_str, - gid = %gid_str, - "Updated /etc/passwd and /etc/group for sandbox identity" - ); - Ok(()) -} - -/// Rewrite the `sandbox` line in `/etc/passwd` with the given UID/GID, -/// or append a new entry if none exists. -#[cfg(unix)] -fn update_passwd_file(uid: &str, gid: &str) -> Result<()> { - rewrite_passwd_at(Path::new("/etc/passwd"), uid, gid) -} - -/// Rewrite the `sandbox` line in `/etc/group` with the given GID, -/// or append a new entry if none exists. -#[cfg(unix)] -fn update_group_file(gid: &str) -> Result<()> { - rewrite_group_at(Path::new("/etc/group"), gid) -} - -#[cfg(unix)] -fn rewrite_passwd_at(path: &Path, uid: &str, gid: &str) -> Result<()> { - let content = std::fs::read_to_string(path).into_diagnostic()?; - - let mut found = false; - let mut lines: Vec = content - .lines() - .map(|line| { - if line.starts_with("sandbox:") { - found = true; - let fields: Vec<&str> = line.split(':').collect(); - if let [name, pass, _, _, gecos, home, shell, ..] = fields.as_slice() { - format!("{name}:{pass}:{uid}:{gid}:{gecos}:{home}:{shell}") - } else { - line.to_string() - } - } else { - line.to_string() - } - }) - .collect(); - - if !found { - lines.push(format!("sandbox:x:{uid}:{gid}::/sandbox:/bin/sh")); - } - - let mut output = lines.join("\n"); - if content.ends_with('\n') || !found { - output.push('\n'); - } - - std::fs::write(path, output).into_diagnostic()?; - Ok(()) -} - -#[cfg(unix)] -fn rewrite_group_at(path: &Path, gid: &str) -> Result<()> { - let content = std::fs::read_to_string(path).into_diagnostic()?; - - let mut found = false; - let mut lines: Vec = content - .lines() - .map(|line| { - if line.starts_with("sandbox:") { - found = true; - let fields: Vec<&str> = line.split(':').collect(); - if let [name, pass, _, members, ..] = fields.as_slice() { - format!("{name}:{pass}:{gid}:{members}") - } else { - line.to_string() - } - } else { - line.to_string() - } - }) - .collect(); - - if !found { - lines.push(format!("sandbox:x:{gid}:")); - } - - let mut output = lines.join("\n"); - if content.ends_with('\n') || !found { - output.push('\n'); - } - - std::fs::write(path, output).into_diagnostic()?; - Ok(()) -} - -/// Recursively chown a directory tree to the given UID/GID. -/// -/// This retains the Kubernetes/OpenShift workspace reconciliation from before -/// OCI image identity fallback. Symlinks are skipped, and read-only nested -/// mounts are not traversed. -#[cfg(unix)] -fn chown_sandbox_home(root: &Path, uid: Option, gid: Option) -> Result<()> { - let meta = std::fs::symlink_metadata(root).into_diagnostic()?; - if meta.file_type().is_symlink() { - return Err(miette::miette!( - "path '{}' is a symlink — refusing to chown (potential privilege escalation)", - root.display() - )); - } - - nix::unistd::chown(root, uid, gid).into_diagnostic()?; - - if meta.is_dir() { - chown_children(root, uid, gid, &nix::unistd::chown)?; - } - - Ok(()) -} - -#[cfg(unix)] -fn prepare_oci_workspace( - root: &Path, - uid: Option, - gid: Option, - supplementary_gids: &[Gid], -) -> Result<()> { - prepare_oci_workspace_with(root, uid, gid, supplementary_gids, &nix::unistd::chown) -} - -/// Validate that selecting an image-provided OCI workdir does not grant the -/// sandbox identity any filesystem authority it lacked in the immutable image. -/// -/// Every path component must be a real directory (never a symlink), every -/// parent must already be traversable, and the final directory must already be -/// writable and traversable. No ownership or mode bits are changed. -#[cfg(unix)] -pub fn validate_oci_workspace( - root: &Path, - uid: Option, - gid: Option, - supplementary_gids: &[Gid], -) -> Result<()> { - let components = validated_workspace_components(root, false)?; - let mut current = PathBuf::from("/"); - validate_workspace_component(¤t, uid, gid, supplementary_gids, false)?; - let last_component = components.len().saturating_sub(1); - for (index, component) in components.into_iter().enumerate() { - current.push(component); - validate_workspace_component( - ¤t, - uid, - gid, - supplementary_gids, - index == last_component, - )?; - } - Ok(()) -} - -/// Validate an image-provided workdir in a clean copy of the supervisor so the -/// main process retains the root authority needed for subsequent setup. -#[cfg(target_os = "linux")] -fn validate_oci_workspace_in_subprocess( - policy: &SandboxPolicy, - resolved_identity: ResolvedProcessIdentity, - workdir: &Path, -) -> Result<()> { - use std::os::unix::process::CommandExt; - - let (uid, gid, supplementary_gids) = resolve_filesystem_identity(policy, resolved_identity)?; - let uid = uid.ok_or_else(|| miette::miette!("workspace validator UID is unresolved"))?; - let gid = gid.ok_or_else(|| miette::miette!("workspace validator GID is unresolved"))?; - let groups = supplementary_gids - .iter() - .map(|group| group.as_raw()) - .collect::>(); - let executable = std::env::current_exe().into_diagnostic()?; - let mut command = std::process::Command::new(executable); - command - .arg("validate-workspace") - .arg("--workdir") - .arg(workdir) - .arg("--expected-uid") - .arg(uid.to_string()) - .arg("--expected-gid") - .arg(gid.to_string()) - .env_clear() - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::piped()); - - // `pre_exec` runs after fork and before exec. These direct credential - // syscalls are async-signal-safe and affect only the one-shot child. - #[allow(unsafe_code)] - unsafe { - command.pre_exec(move || { - if libc::setgroups(groups.len(), groups.as_ptr()) != 0 - || libc::setgid(gid.as_raw()) != 0 - || libc::setuid(uid.as_raw()) != 0 - { - return Err(std::io::Error::last_os_error()); - } - Ok(()) - }); - } - - let output = command.output().into_diagnostic()?; - if output.status.success() { - return Ok(()); - } - - let diagnostic = String::from_utf8_lossy(&output.stderr); - let diagnostic = diagnostic.trim(); - if diagnostic.is_empty() { - return Err(miette::miette!( - "image workspace validation failed with status {}", - output.status - )); - } - Err(miette::miette!( - "image workspace validation failed: {diagnostic}" - )) -} - -#[cfg(unix)] -fn validate_workspace_component( - path: &Path, - uid: Option, - gid: Option, - supplementary_gids: &[Gid], - is_workspace: bool, -) -> Result<()> { - let metadata = std::fs::symlink_metadata(path).map_err(|error| { - if error.kind() == std::io::ErrorKind::NotFound { - miette::miette!( - "image workspace path component '{}' does not exist", - path.display() - ) - } else { - miette::miette!( - "failed to inspect image workspace path component '{}': {error}", - path.display() - ) - } - })?; - if metadata.file_type().is_symlink() { - return Err(miette::miette!( - "workspace path component '{}' is a symlink — refusing to follow it", - path.display() - )); - } - if !metadata.is_dir() { - return Err(miette::miette!( - "workspace path component '{}' is not a directory", - path.display() - )); - } - let required = if is_workspace { 0o3 } else { 0o1 }; - if !identity_has_permissions(&metadata, uid, gid, supplementary_gids, required) { - let requirement = if is_workspace { - "writable and traversable" - } else { - "traversable" - }; - return Err(miette::miette!( - "workspace path component '{}' is not {requirement} by the sandbox identity in the image", - path.display() - )); - } - Ok(()) -} - #[cfg(target_os = "linux")] pub fn validate_oci_workspace_as_effective_identity(root: &Path) -> Result<()> { use rustix::fs::{Access, AtFlags, FileType, Mode, OFlags}; - let components = validated_workspace_components(root, false)?; + let components = validated_workspace_components(root)?; let open_flags = OFlags::PATH | OFlags::DIRECTORY | OFlags::NOFOLLOW | OFlags::CLOEXEC; let mut current_path = PathBuf::from("/"); let mut current_fd = rustix::fs::open("/", open_flags, Mode::empty()).into_diagnostic()?; @@ -1554,91 +970,21 @@ fn validate_effective_workspace_write(fd: &impl std::os::fd::AsFd, path: &Path) )) } -/// Prepare only the resolved `OpenShell` workspace directory itself. -/// -/// Image-provided children retain their declared ownership. This avoids -/// crossing symlinks or user-provided nested mounts. -#[cfg(unix)] -fn prepare_oci_workspace_with( - root: &Path, - uid: Option, - gid: Option, - supplementary_gids: &[Gid], - do_chown: &impl Fn(&Path, Option, Option) -> nix::Result<()>, -) -> Result<()> { - let components = validated_workspace_components(root, true)?; - - let last_component = components.len().saturating_sub(1); - let mut current = PathBuf::from("/"); - for (index, component) in components.into_iter().enumerate() { - current.push(component); - match std::fs::symlink_metadata(¤t) { - Ok(metadata) if metadata.file_type().is_symlink() => { - return Err(miette::miette!( - "workspace path component '{}' is a symlink — refusing to follow it", - current.display() - )); - } - Ok(metadata) if !metadata.is_dir() => { - return Err(miette::miette!( - "workspace path component '{}' is not a directory", - current.display() - )); - } - Ok(metadata) => { - if index != last_component - && !identity_can_traverse(&metadata, uid, gid, supplementary_gids) - { - return Err(miette::miette!( - "workspace parent '{}' is not traversable by the sandbox identity", - current.display() - )); - } - } - Err(error) if error.kind() == std::io::ErrorKind::NotFound => { - std::fs::create_dir(¤t).into_diagnostic()?; - std::fs::set_permissions(¤t, std::fs::Permissions::from_mode(0o755)) - .into_diagnostic()?; - } - Err(error) => return Err(error).into_diagnostic(), - } - } - - do_chown(root, uid, gid).into_diagnostic()?; - - let metadata = std::fs::symlink_metadata(root).into_diagnostic()?; - let mode = metadata.permissions().mode() & 0o7777; - if mode & 0o300 != 0o300 { - std::fs::set_permissions(root, std::fs::Permissions::from_mode(mode | 0o300)) - .into_diagnostic()?; - } - Ok(()) -} - -#[cfg(unix)] -fn validated_workspace_components( - root: &Path, - allow_managed_fallback: bool, -) -> Result> { - let root_str = root - .to_str() - .ok_or_else(|| miette::miette!("workspace path must be valid UTF-8"))?; - let validated_root = openshell_core::driver_mounts::resolve_oci_workspace_root(root_str) - .map_err(|error| miette::miette!(error))?; - if Path::new(&validated_root) != root - || (!allow_managed_fallback - && validated_root == openshell_core::driver_mounts::DEFAULT_WORKSPACE_ROOT) - { - return Err(miette::miette!( - "workspace path '{}' must be a normalized absolute {}path", - root.display(), - if allow_managed_fallback { - "non-root " - } else { - "non-fallback " - } - )); - } +#[cfg(target_os = "linux")] +fn validated_workspace_components(root: &Path) -> Result> { + let root_str = root + .to_str() + .ok_or_else(|| miette::miette!("workspace path must be valid UTF-8"))?; + let validated_root = openshell_core::driver_mounts::resolve_oci_workspace_root(root_str) + .map_err(|error| miette::miette!(error))?; + if Path::new(&validated_root) != root + || validated_root == openshell_core::driver_mounts::DEFAULT_WORKSPACE_ROOT + { + return Err(miette::miette!( + "workspace path '{}' must be a normalized absolute non-fallback path", + root.display() + )); + } root.components() .skip(1) @@ -1652,476 +998,6 @@ fn validated_workspace_components( .collect() } -#[cfg(unix)] -fn identity_can_traverse( - metadata: &std::fs::Metadata, - uid: Option, - gid: Option, - supplementary_gids: &[Gid], -) -> bool { - identity_has_permissions(metadata, uid, gid, supplementary_gids, 0o1) -} - -#[cfg(unix)] -fn identity_has_permissions( - metadata: &std::fs::Metadata, - uid: Option, - gid: Option, - supplementary_gids: &[Gid], - required: u32, -) -> bool { - let user_id = uid.unwrap_or_else(nix::unistd::geteuid).as_raw(); - if user_id == 0 { - return true; - } - - let group_id = gid.unwrap_or_else(nix::unistd::getegid).as_raw(); - let mode = metadata.permissions().mode(); - if metadata.uid() == user_id { - mode & (required << 6) == required << 6 - } else if metadata.gid() == group_id - || supplementary_gids - .iter() - .any(|supplementary_gid| supplementary_gid.as_raw() == metadata.gid()) - { - mode & (required << 3) == required << 3 - } else { - mode & required == required - } -} - -#[cfg(not(any( - target_os = "aix", - target_os = "haiku", - target_os = "illumos", - target_os = "ios", - target_os = "macos", - target_os = "redox", - target_os = "solaris" -)))] -fn named_user_supplementary_groups(user_name: &str, primary_gid: Gid) -> Result> { - let user_name = CString::new(user_name).map_err(|_| miette::miette!("Invalid user name"))?; - nix::unistd::getgrouplist(user_name.as_c_str(), primary_gid).into_diagnostic() -} - -#[cfg(any( - target_os = "aix", - target_os = "haiku", - target_os = "illumos", - target_os = "ios", - target_os = "macos", - target_os = "redox", - target_os = "solaris" -))] -#[allow(clippy::unnecessary_wraps)] -fn named_user_supplementary_groups(_user_name: &str, _primary_gid: Gid) -> Result> { - // Privilege dropping does not call initgroups on these targets. - Ok(Vec::new()) -} - -#[cfg(unix)] -fn chown_children( - dir: &Path, - uid: Option, - gid: Option, - do_chown: &impl Fn(&Path, Option, Option) -> nix::Result<()>, -) -> Result<()> { - match std::fs::read_dir(dir) { - Ok(entries) => { - for entry in entries { - let entry = entry.into_diagnostic()?; - chown_recursive(&entry.path(), uid, gid, do_chown)?; - } - } - Err(error) => { - debug!( - path = %dir.display(), - %error, - "Cannot list directory during sandbox home chown" - ); - } - } - Ok(()) -} - -#[cfg(unix)] -fn chown_recursive( - path: &Path, - uid: Option, - gid: Option, - do_chown: &impl Fn(&Path, Option, Option) -> nix::Result<()>, -) -> Result<()> { - let meta = std::fs::symlink_metadata(path).into_diagnostic()?; - if meta.file_type().is_symlink() { - debug!(path = %path.display(), "Skipping symlink during sandbox home chown"); - return Ok(()); - } - - if let Err(error) = do_chown(path, uid, gid) { - if error == nix::errno::Errno::EROFS { - debug!(path = %path.display(), "Skipping read-only path during sandbox home chown"); - return Ok(()); - } - return Err(error).into_diagnostic(); - } - - if meta.is_dir() { - chown_children(path, uid, gid, do_chown)?; - } - - Ok(()) -} - -/// Prepare filesystem for the sandboxed process. -/// -/// Creates `read_write` directories if they don't exist and sets ownership -/// on newly-created paths to the configured sandbox user/group. This runs as -/// the supervisor (root) before forking the child process. -/// -/// Accepts both name-based identities (resolved via `/etc/passwd`) and numeric -/// UIDs/GIDs (passed directly to `chown` without a passwd lookup). -#[cfg(unix)] -pub fn prepare_filesystem(policy: &SandboxPolicy) -> Result<()> { - prepare_filesystem_with_identity(policy, ResolvedProcessIdentity::default(), None, false) -} - -#[cfg(unix)] -pub fn prepare_filesystem_with_identity( - policy: &SandboxPolicy, - resolved_identity: ResolvedProcessIdentity, - workdir: Option<&str>, - prepare_workspace: bool, -) -> Result<()> { - use nix::unistd::chown; - - // If no user/group configured, nothing to do - if policy - .process - .run_as_user - .as_deref() - .is_none_or(str::is_empty) - && policy - .process - .run_as_group - .as_deref() - .is_none_or(str::is_empty) - { - return Ok(()); - } - - let (uid, gid, supplementary_gids) = resolve_filesystem_identity(policy, resolved_identity)?; - - // Docker owns workspace resolution and must make the selected root usable - // by the final effective identity, including when both policy identity - // fields were explicit. Validate it before processing any user-authored - // read-write paths so an unsafe image path fails first. Other drivers - // retain their preparation. - if prepare_workspace { - let workspace = workdir.ok_or_else(|| { - miette::miette!("local container driver did not supply a workspace workdir") - })?; - let workspace = Path::new(workspace); - if workspace == Path::new(openshell_core::driver_mounts::DEFAULT_WORKSPACE_ROOT) { - info!(path = %workspace.display(), ?uid, ?gid, "Preparing managed workspace"); - prepare_oci_workspace(workspace, uid, gid, &supplementary_gids)?; - } else { - info!(path = %workspace.display(), ?uid, ?gid, "Validating image workspace authority"); - #[cfg(target_os = "linux")] - validate_oci_workspace_in_subprocess(policy, resolved_identity, workspace)?; - #[cfg(not(target_os = "linux"))] - validate_oci_workspace(workspace, uid, gid, &supplementary_gids)?; - } - } - - // Create missing read_write paths and only chown the ones we created. - for path in &policy.filesystem.read_write { - if prepare_read_write_path(path)? { - debug!( - path = %path.display(), - ?uid, - ?gid, - "Setting ownership on newly created read_write path" - ); - chown(path, uid, gid).into_diagnostic()?; - } - } - - // Retain the existing Kubernetes/OpenShift behavior for driver-injected - // numeric identities. Docker clears this variable and does not receive - // identity-specific workspace preparation. - if std::env::var(openshell_core::sandbox_env::SANDBOX_UID).is_ok_and(|uid| !uid.is_empty()) { - let sandbox_home = Path::new("/sandbox"); - if sandbox_home.exists() { - info!(?uid, ?gid, "Chowning /sandbox for driver-injected UID/GID"); - chown_sandbox_home(sandbox_home, uid, gid)?; - } - } - - Ok(()) -} - -#[cfg(unix)] -fn resolve_filesystem_identity( - policy: &SandboxPolicy, - resolved_identity: ResolvedProcessIdentity, -) -> Result<(Option, Option, Vec)> { - let user_name = policy - .process - .run_as_user - .as_deref() - .filter(|name| !name.is_empty()); - let group_name = policy - .process - .run_as_group - .as_deref() - .filter(|name| !name.is_empty()); - - let uid = match resolved_identity.uid() { - Some(uid) => Some(Uid::from_raw(uid)), - None => match user_name { - Some(name) if name.parse::().is_ok() => { - Some(Uid::from_raw(name.parse().into_diagnostic()?)) - } - Some(name) => User::from_name(name).into_diagnostic()?.map(|u| u.uid), - _ => None, - }, - }; - - // Resolve GID: numeric values are passed directly; names resolve via group. - let gid = match resolved_identity.gid() { - Some(gid) => Some(Gid::from_raw(gid)), - None => match group_name { - Some(name) if name.parse::().is_ok() => { - Some(Gid::from_raw(name.parse().into_diagnostic()?)) - } - Some(name) => Group::from_name(name).into_diagnostic()?.map(|g| g.gid), - _ => None, - }, - }; - - let supplementary_gids = match user_name { - Some(name) if name.parse::().is_err() => { - let primary_gid = if let Some(gid) = gid { - gid - } else { - let uid = - uid.ok_or_else(|| miette::miette!("Failed to resolve sandbox user '{name}'"))?; - User::from_uid(uid) - .into_diagnostic()? - .ok_or_else(|| miette::miette!("Failed to resolve user from UID {uid}"))? - .gid - }; - if resolved_identity.uid().is_some() { - crate::identity::resolve_oci_supplementary_gids(name, primary_gid.as_raw())? - .into_iter() - .map(Gid::from_raw) - .collect() - } else { - named_user_supplementary_groups(name, primary_gid)? - } - } - _ => Vec::new(), - }; - - Ok((uid, gid, supplementary_gids)) -} - -#[cfg(not(unix))] -pub fn prepare_filesystem(_policy: &SandboxPolicy) -> Result<()> { - Ok(()) -} - -// `effective_gid`/`effective_uid` are intentionally parallel names (same role -// for different identifiers) and the noise from renaming would obscure intent. -#[cfg(unix)] -#[allow(clippy::similar_names)] -pub fn drop_privileges(policy: &SandboxPolicy) -> Result<()> { - drop_privileges_with_identity(policy, ResolvedProcessIdentity::default()) -} - -#[cfg(unix)] -#[allow(clippy::similar_names)] -pub fn drop_privileges_with_identity( - policy: &SandboxPolicy, - resolved_identity: ResolvedProcessIdentity, -) -> Result<()> { - let user_name = match policy.process.run_as_user.as_deref() { - Some(name) if !name.is_empty() => Some(name), - _ => None, - }; - let group_name = match policy.process.run_as_group.as_deref() { - Some(name) if !name.is_empty() => Some(name), - _ => None, - }; - - // If no user/group is configured and we are running as root, fall back to - // "sandbox:sandbox" instead of silently keeping root. This covers the - // local/dev-mode path for drivers that provide no identity metadata. - // For non-root runtimes, the no-op is safe -- we are already unprivileged. - if user_name.is_none() && group_name.is_none() { - if nix::unistd::geteuid().is_root() { - let mut fallback = policy.clone(); - fallback.process.run_as_user = Some("sandbox".into()); - fallback.process.run_as_group = Some("sandbox".into()); - return drop_privileges_with_identity(&fallback, resolved_identity); - } - return Ok(()); - } - - // Resolve UID: numeric values are used directly; names resolve via passwd. - let target_uid = match resolved_identity.uid() { - Some(uid) => Uid::from_raw(uid), - None => match user_name { - Some(name) if name.parse::().is_ok() => { - Uid::from_raw(name.parse().into_diagnostic()?) - } - Some(name) => { - User::from_name(name) - .into_diagnostic()? - .ok_or_else(|| miette::miette!("Sandbox user not found: {name}"))? - .uid - } - None => nix::unistd::geteuid(), - }, - }; - - // Resolve group: if a numeric GID is configured use it directly. - // Otherwise try name resolution, then fall back to current user's primary group. - let target_gid = match resolved_identity.gid() { - Some(gid) => Gid::from_raw(gid), - None => match group_name { - Some(name) if name.parse::().is_ok() => { - Gid::from_raw(name.parse().into_diagnostic()?) - } - Some(name) => { - Group::from_name(name) - .into_diagnostic()? - .ok_or_else(|| miette::miette!("Sandbox group not found: {name}"))? - .gid - } - None => match target_uid.as_raw() { - 0 => nix::unistd::getegid(), - _ => Group::from_gid( - User::from_uid(target_uid) - .into_diagnostic()? - .ok_or_else(|| { - miette::miette!("Failed to resolve user from UID {target_uid}") - })? - .gid, - ) - .into_diagnostic()? - .map_or_else(nix::unistd::getegid, |g| g.gid), - }, - }, - }; - - // Resolve the name for initgroups only for the existing explicit-policy - // path. OCI-derived users carry a numeric UID from the bounded parser and - // must not be looked up again through NSS. - let user_name_is_numeric = user_name.is_some_and(|n| n.parse::().is_ok()); - let initgroups_name = - if user_name.is_some() && !user_name_is_numeric && resolved_identity.uid().is_none() { - Some( - User::from_uid(target_uid) - .into_diagnostic()? - .ok_or_else(|| { - miette::miette!("Failed to resolve user record for UID {target_uid}") - })? - .name, - ) - } else { - None - }; - - if target_uid != nix::unistd::geteuid() { - if resolved_identity.uses_oci_user_fallback() { - // OCI named users use the bounded /etc/group parser shared with - // workspace validation. Numeric OCI users resolve to an empty - // list. Never retain the root supervisor's inherited groups. - #[cfg(not(any( - target_os = "macos", - target_os = "ios", - target_os = "haiku", - target_os = "redox" - )))] - { - let (_, _, supplementary_gids) = - resolve_filesystem_identity(policy, resolved_identity)?; - nix::unistd::setgroups(&supplementary_gids).into_diagnostic()?; - } - } else if let Some(ref user_name) = initgroups_name { - let user_cstr = CString::new(user_name.as_str()) - .map_err(|_| miette::miette!("Invalid user name"))?; - #[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "haiku", - target_os = "redox" - ))] - { - let _ = user_cstr; - } - #[cfg(not(any( - target_os = "macos", - target_os = "ios", - target_os = "haiku", - target_os = "redox" - )))] - { - nix::unistd::initgroups(user_cstr.as_c_str(), target_gid).into_diagnostic()?; - } - } - } - - if target_gid != nix::unistd::getegid() { - nix::unistd::setgid(target_gid).into_diagnostic()?; - } - - // Verify effective GID actually changed (defense-in-depth, CWE-250 / CERT POS37-C) - let effective_gid = nix::unistd::getegid(); - if effective_gid != target_gid { - return Err(miette::miette!( - "Privilege drop verification failed: expected effective GID {}, got {}", - target_gid, - effective_gid - )); - } - - #[cfg(target_os = "linux")] - if nix::unistd::geteuid().is_root() { - drop_capability_bounding_set()?; - } - - if user_name.is_some() { - if target_uid != nix::unistd::geteuid() { - nix::unistd::setuid(target_uid).into_diagnostic()?; - } - - // Verify effective UID actually changed (defense-in-depth, CWE-250 / CERT POS37-C) - let effective_uid = nix::unistd::geteuid(); - if effective_uid != target_uid { - return Err(miette::miette!( - "Privilege drop verification failed: expected effective UID {}, got {}", - target_uid, - effective_uid - )); - } - - // Verify root cannot be re-acquired (CERT POS37-C hardening). - // If we dropped from root, setuid(0) must fail; success means privileges - // were not fully relinquished. - if nix::unistd::setuid(Uid::from_raw(0)).is_ok() && target_uid.as_raw() != 0 { - return Err(miette::miette!( - "Privilege drop verification failed: process can still re-acquire root (UID 0) \ - after switching to UID {}", - target_uid - )); - } - } - - Ok(()) -} - /// Process exit status. #[derive(Debug, Clone, Copy)] pub struct ProcessStatus { @@ -2188,8 +1064,12 @@ mod tests { use openshell_core::policy::{ FilesystemPolicy, LandlockPolicy, NetworkPolicy, ProcessPolicy, SandboxPolicy, }; + #[cfg(target_os = "linux")] + use std::ffi::CString; #[cfg(unix)] use std::mem::size_of; + #[cfg(target_os = "linux")] + use std::os::unix::fs::PermissionsExt; use std::process::Stdio as StdStdio; /// Helper to create a minimal `SandboxPolicy` with the given process policy. @@ -2294,333 +1174,6 @@ mod tests { } } - /// Unknown names may yield `Ok(None)` (`… not found …`) or `Err` when NSS fails first - /// (e.g. `ENOENT: No such file or directory`). - fn assert_unknown_identity_lookup_failed(msg: &str) { - assert!( - msg.contains("not found") - || msg.contains("ENOENT") - || msg.contains("No such file or directory"), - "expected unknown user/group lookup failure (…not found… or ENOENT): {msg}" - ); - } - - #[test] - #[cfg(unix)] - fn explicit_identity_accepts_non_root_system_ids() { - let policy = policy_with_process(ProcessPolicy { - run_as_user: Some("101".into()), - run_as_group: Some("102".into()), - }); - - assert!(validate_sandbox_user(&policy).is_ok()); - assert!(validate_sandbox_group(&policy).is_ok()); - } - - #[test] - #[cfg(unix)] - fn resolved_oci_identity_accepts_non_root_system_ids() { - let policy = policy_with_process(ProcessPolicy { - run_as_user: Some("app".into()), - run_as_group: Some("staff".into()), - }); - let resolved = ResolvedProcessIdentity::new(Some(101), Some(102)); - - assert!(validate_sandbox_user_with_identity(&policy, resolved).is_ok()); - assert!(validate_sandbox_group_with_identity(&policy, resolved).is_ok()); - } - - #[test] - #[cfg(unix)] - fn completed_runtime_identity_rejects_numeric_root() { - let root_user = policy_with_process(ProcessPolicy { - run_as_user: Some("0".into()), - run_as_group: Some("102".into()), - }); - let root_group = policy_with_process(ProcessPolicy { - run_as_user: Some("101".into()), - run_as_group: Some("0".into()), - }); - - assert!(validate_sandbox_user(&root_user).is_err()); - assert!(validate_sandbox_group(&root_group).is_err()); - } - - #[test] - #[cfg(unix)] - fn resolved_oci_components_do_not_repeat_nss_validation() { - let policy = policy_with_process(ProcessPolicy { - run_as_user: Some("__oci_name_not_in_host_nss__".into()), - run_as_group: Some("__oci_group_not_in_host_nss__".into()), - }); - let resolved = ResolvedProcessIdentity::new(Some(1234), Some(1235)); - - assert!(validate_sandbox_user_with_identity(&policy, resolved).is_ok()); - assert!(validate_sandbox_group_with_identity(&policy, resolved).is_ok()); - } - - #[test] - #[cfg(unix)] - fn explicit_policy_components_keep_existing_validation_path() { - let policy = policy_with_process(ProcessPolicy { - run_as_user: Some("__explicit_name_not_in_host_nss__".into()), - run_as_group: Some("__oci_group_not_in_host_nss__".into()), - }); - let resolved = ResolvedProcessIdentity::new(None, Some(1235)); - - assert!(validate_sandbox_user_with_identity(&policy, resolved).is_err()); - assert!(validate_sandbox_group_with_identity(&policy, resolved).is_ok()); - } - - #[cfg(target_os = "linux")] - fn capability_bounding_set_clear_available() -> bool { - capctl::caps::CapState::get_current() - .is_ok_and(|state| state.effective.has(capctl::caps::Cap::SETPCAP)) - || capctl::caps::bounding::probe().is_empty() - } - - #[test] - #[cfg(target_os = "linux")] - fn capability_bounding_set_clear_accepts_empty_eperm() { - let remaining = capctl::caps::CapSet::empty(); - - assert!( - validate_capability_bounding_set_clear( - Err(capctl::Error::from_code(libc::EPERM)), - remaining, - || Ok(()), - ) - .is_ok() - ); - } - - #[test] - #[cfg(target_os = "linux")] - fn capability_bounding_set_clear_rejects_nonempty_eperm() { - let mut remaining = capctl::caps::CapSet::empty(); - remaining.add(capctl::caps::Cap::CHOWN); - - let result = validate_capability_bounding_set_clear( - Err(capctl::Error::from_code(libc::EPERM)), - remaining, - || panic!("unknown capabilities should not be checked when known caps remain"), - ); - - assert!(result.is_err()); - assert!( - result - .unwrap_err() - .to_string() - .contains("Failed to clear child capability bounding set") - ); - } - - #[test] - #[cfg(target_os = "linux")] - fn capability_bounding_set_clear_rejects_nonempty_success() { - let mut remaining = capctl::caps::CapSet::empty(); - remaining.add(capctl::caps::Cap::CHOWN); - - let result = validate_capability_bounding_set_clear(Ok(()), remaining, || { - panic!("unknown capabilities should not be checked when known caps remain") - }); - - assert!(result.is_err()); - assert!( - result - .unwrap_err() - .to_string() - .contains("capabilities remain raised") - ); - } - - #[test] - #[cfg(target_os = "linux")] - fn capability_bounding_set_clear_rejects_unknown_eperm() { - let remaining = capctl::caps::CapSet::empty(); - - let result = validate_capability_bounding_set_clear( - Err(capctl::Error::from_code(libc::EPERM)), - remaining, - || Err(capctl::Error::from_code(libc::EPERM)), - ); - - assert!(result.is_err()); - assert!( - result - .unwrap_err() - .to_string() - .contains("Failed to clear unknown child capability bounding set entries") - ); - } - - #[test] - #[cfg(target_os = "linux")] - fn capability_probe_child() { - if std::env::var_os("OPENSHELL_TEST_PROBE_CHILD_CAPS").is_none() { - return; - } - - assert!( - capctl::caps::bounding::probe().is_empty(), - "child CapBnd should be empty after exec" - ); - } - - #[test] - fn drop_privileges_noop_when_no_user_or_group() { - let policy = policy_with_process(ProcessPolicy { - run_as_user: None, - run_as_group: None, - }); - if nix::unistd::geteuid().is_root() { - // As root, drop_privileges falls back to "sandbox:sandbox". - // If that user exists, it succeeds; if not (e.g. CI), it - // must error rather than silently keep root. - let has_sandbox = User::from_name("sandbox").ok().flatten().is_some(); - assert_eq!(drop_privileges(&policy).is_ok(), has_sandbox); - } else { - assert!(drop_privileges(&policy).is_ok()); - } - } - - #[test] - fn drop_privileges_noop_when_empty_strings() { - let policy = policy_with_process(ProcessPolicy { - run_as_user: Some(String::new()), - run_as_group: Some(String::new()), - }); - if nix::unistd::geteuid().is_root() { - let has_sandbox = User::from_name("sandbox").ok().flatten().is_some(); - assert_eq!(drop_privileges(&policy).is_ok(), has_sandbox); - } else { - assert!(drop_privileges(&policy).is_ok()); - } - } - - #[test] - fn drop_privileges_succeeds_for_current_group() { - // Set only run_as_group (no run_as_user) so that initgroups() is not - // called. initgroups(3) requires CAP_SETGID/root even when the target - // is the current user, so it cannot be exercised without elevated - // privileges. This test covers the setgid() + GID post-condition - // verification path without needing root. - let current_group = Group::from_gid(nix::unistd::getegid()) - .expect("getgrgid") - .expect("current group entry"); - - let policy = policy_with_process(ProcessPolicy { - run_as_user: None, - run_as_group: Some(current_group.name), - }); - - let result = drop_privileges(&policy); - #[cfg(target_os = "linux")] - { - if nix::unistd::geteuid().is_root() && !capability_bounding_set_clear_available() { - let msg = format!("{}", result.unwrap_err()); - assert!( - msg.contains("Failed to clear child capability bounding set"), - "unexpected failure: {msg}" - ); - return; - } - } - assert!(result.is_ok(), "drop_privileges failed: {result:?}"); - } - - #[test] - #[cfg(target_os = "linux")] - #[allow(unsafe_code)] - fn drop_privileges_clears_bounding_set_for_spawned_child_when_permitted() { - use std::os::unix::process::CommandExt; - - if !capability_bounding_set_clear_available() { - eprintln!( - "skipping: CAP_SETPCAP is not effective and the capability bounding set is nonempty" - ); - return; - } - - let current_group = Group::from_gid(nix::unistd::getegid()) - .expect("getgrgid") - .expect("current group entry"); - - let policy = policy_with_process(ProcessPolicy { - run_as_user: None, - run_as_group: Some(current_group.name), - }); - - let mut cmd = std::process::Command::new(std::env::current_exe().expect("current exe")); - cmd.arg("capability_probe_child") - .arg("--nocapture") - .env("OPENSHELL_TEST_PROBE_CHILD_CAPS", "1") - .stdin(StdStdio::null()) - .stdout(StdStdio::piped()) - .stderr(StdStdio::piped()); - - unsafe { - cmd.pre_exec(move || { - drop_privileges(&policy).map_err(|err| std::io::Error::other(err.to_string())) - }); - } - - let output = cmd.output().expect("spawn child status probe"); - assert!( - output.status.success(), - "status probe failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - } - - #[test] - #[ignore = "initgroups(3) requires CAP_SETGID; run as root: sudo cargo test -- --ignored"] - fn drop_privileges_succeeds_for_current_user() { - // Exercises the full privilege-drop path including initgroups(), - // setgid(), setuid(), and the root-reacquisition check. Requires - // CAP_SETGID (root) because initgroups(3) calls setgroups(2) - // internally. Fixes: https://github.com/NVIDIA/OpenShell/issues/622 - let current_user = User::from_uid(nix::unistd::geteuid()) - .expect("getpwuid") - .expect("current user entry"); - let current_group = Group::from_gid(nix::unistd::getegid()) - .expect("getgrgid") - .expect("current group entry"); - - let policy = policy_with_process(ProcessPolicy { - run_as_user: Some(current_user.name), - run_as_group: Some(current_group.name), - }); - - assert!(drop_privileges(&policy).is_ok()); - } - - #[test] - fn drop_privileges_fails_for_nonexistent_user() { - let policy = policy_with_process(ProcessPolicy { - run_as_user: Some("__nonexistent_test_user_42__".to_string()), - run_as_group: None, - }); - - let result = drop_privileges(&policy); - assert!(result.is_err()); - let msg = format!("{}", result.unwrap_err()); - assert_unknown_identity_lookup_failed(&msg); - } - - #[test] - fn drop_privileges_fails_for_nonexistent_group() { - let policy = policy_with_process(ProcessPolicy { - run_as_user: None, - run_as_group: Some("__nonexistent_test_group_42__".to_string()), - }); - - let result = drop_privileges(&policy); - assert!(result.is_err()); - let msg = format!("{}", result.unwrap_err()); - assert_unknown_identity_lookup_failed(&msg); - } - #[cfg(unix)] #[allow(unsafe_code)] fn probe_hardened_child(probe: unsafe fn() -> i64) -> i64 { @@ -2697,416 +1250,75 @@ mod tests { } #[test] - #[cfg(target_os = "linux")] - fn harden_child_process_marks_process_nondumpable() { - assert_eq!(probe_hardened_child(dumpable_flag_probe), 0); - } - - #[test] - #[cfg(target_os = "linux")] - fn parse_pids_max_detects_limited_runtime() { - assert_eq!( - parse_pids_max("2048\n"), - RuntimePidLimitStatus::Limited(2048) - ); - } - - #[test] - #[cfg(target_os = "linux")] - fn parse_pids_max_detects_unlimited_runtime() { - assert_eq!(parse_pids_max("max\n"), RuntimePidLimitStatus::Unlimited); - } - - #[test] - #[cfg(target_os = "linux")] - fn parse_pids_max_reports_invalid_values() { - let status = parse_pids_max("not-a-number\n"); - assert!(matches!(status, RuntimePidLimitStatus::Unavailable(_))); - } - - #[test] - #[cfg(target_os = "linux")] - fn pid_limit_require_mode_rejects_missing_guardrail_statuses() { - for status in [ - RuntimePidLimitStatus::Unlimited, - RuntimePidLimitStatus::Unavailable("missing".to_string()), - ] { - let result = check_runtime_pid_limit_status(status, RuntimePidLimitMode::Require); - assert!(result.is_err()); - } - } - - #[test] - #[cfg(target_os = "linux")] - fn pid_limit_warn_mode_accepts_missing_guardrail_statuses() { - for status in [ - RuntimePidLimitStatus::Unlimited, - RuntimePidLimitStatus::Unavailable("missing".to_string()), - ] { - let result = check_runtime_pid_limit_status(status, RuntimePidLimitMode::Warn); - assert!(result.is_ok()); - } - } - - #[tokio::test] - async fn inject_provider_env_sets_placeholder_values() { - let mut cmd = Command::new("/usr/bin/env"); - cmd.stdin(StdStdio::null()) - .stdout(StdStdio::piped()) - .stderr(StdStdio::null()); - - let provider_env = std::iter::once(( - "ANTHROPIC_API_KEY".to_string(), - "openshell:resolve:env:ANTHROPIC_API_KEY".to_string(), - )) - .collect(); - - inject_provider_env(&mut cmd, &provider_env); - - let output = cmd.output().await.expect("spawn env"); - let stdout = String::from_utf8(output.stdout).expect("utf8"); - assert!(stdout.contains("ANTHROPIC_API_KEY=openshell:resolve:env:ANTHROPIC_API_KEY")); - } - - #[cfg(unix)] - fn sandbox_policy_with_read_write( - path: PathBuf, - run_as_user: Option, - run_as_group: Option, - ) -> SandboxPolicy { - SandboxPolicy { - version: 1, - filesystem: FilesystemPolicy { - read_only: vec![], - read_write: vec![path], - include_workdir: false, - }, - network: NetworkPolicy::default(), - landlock: LandlockPolicy::default(), - process: ProcessPolicy { - run_as_user, - run_as_group, - }, - } - } - - #[cfg(unix)] - #[test] - fn prepare_read_write_path_creates_missing_directory() { - let dir = tempfile::tempdir().unwrap(); - let missing = dir.path().join("missing").join("nested"); - - assert!(prepare_read_write_path(&missing).unwrap()); - assert!(missing.is_dir()); - } - - #[cfg(unix)] - #[test] - fn prepare_read_write_path_preserves_existing_directory() { - let dir = tempfile::tempdir().unwrap(); - let existing = dir.path().join("existing"); - std::fs::create_dir(&existing).unwrap(); - - assert!(!prepare_read_write_path(&existing).unwrap()); - assert!(existing.is_dir()); - } - - #[cfg(unix)] - #[test] - fn prepare_read_write_path_rejects_symlink() { - use std::os::unix::fs::symlink; - - let dir = tempfile::tempdir().unwrap(); - let target = dir.path().join("target"); - let link = dir.path().join("link"); - std::fs::create_dir(&target).unwrap(); - symlink(&target, &link).unwrap(); - - let error = prepare_read_write_path(&link).unwrap_err(); - assert!( - error - .to_string() - .contains("is a symlink — refusing to chown"), - "unexpected error: {error}" - ); - } - - #[cfg(unix)] - #[test] - fn prepare_filesystem_skips_chown_for_existing_read_write_paths() { - use std::os::unix::fs::MetadataExt; - - if nix::unistd::geteuid().is_root() { - return; - } - - let Ok(Some(current_user)) = User::from_uid(nix::unistd::geteuid()) else { - eprintln!("skipping: current UID has no /etc/passwd entry"); - return; - }; - let restricted_group = Group::from_gid(Gid::from_raw(0)) - .unwrap() - .expect("gid 0 group entry"); - if restricted_group.gid == nix::unistd::getegid() { - return; - } - - let dir = tempfile::tempdir().unwrap(); - let existing = dir.path().join("existing"); - std::fs::create_dir(&existing).unwrap(); - let before = std::fs::metadata(&existing).unwrap(); - - let policy = sandbox_policy_with_read_write( - existing.clone(), - Some(current_user.name), - Some(restricted_group.name), - ); - - prepare_filesystem(&policy).expect("existing path should not be re-owned"); - - let after = std::fs::metadata(&existing).unwrap(); - assert_eq!(after.uid(), before.uid()); - assert_eq!(after.gid(), before.gid()); - } - - #[cfg(unix)] - #[test] - #[allow(clippy::similar_names)] - fn chown_sandbox_home_changes_ownership_recursively() { - use std::os::unix::fs::MetadataExt; - - let dir = tempfile::tempdir().unwrap(); - let root = dir.path().join("sandbox"); - std::fs::create_dir(&root).unwrap(); - std::fs::write(root.join("file.txt"), "hello").unwrap(); - std::fs::create_dir(root.join("subdir")).unwrap(); - std::fs::write(root.join("subdir").join("nested.txt"), "world").unwrap(); - - let expected_uid = nix::unistd::geteuid(); - let expected_gid = nix::unistd::getegid(); - chown_sandbox_home(&root, Some(expected_uid), Some(expected_gid)).unwrap(); - - for path in &[ - root.clone(), - root.join("file.txt"), - root.join("subdir"), - root.join("subdir").join("nested.txt"), - ] { - let meta = std::fs::metadata(path).unwrap(); - assert_eq!(meta.uid(), expected_uid.as_raw()); - assert_eq!(meta.gid(), expected_gid.as_raw()); - } - } - - #[cfg(unix)] - #[test] - fn chown_sandbox_home_rejects_symlink_root() { - use std::os::unix::fs::symlink; - - let dir = tempfile::tempdir().unwrap(); - let target = dir.path().join("real"); - let link = dir.path().join("link"); - std::fs::create_dir(&target).unwrap(); - symlink(&target, &link).unwrap(); - - let err = chown_sandbox_home( - &link, - Some(nix::unistd::geteuid()), - Some(nix::unistd::getegid()), - ) - .unwrap_err(); - assert!( - err.to_string().contains("symlink"), - "expected symlink rejection: {err}" - ); - } - - #[cfg(unix)] - #[test] - fn chown_sandbox_home_skips_symlink_children() { - use std::os::unix::fs::symlink; - - let dir = tempfile::tempdir().unwrap(); - let root = dir.path().join("sandbox"); - std::fs::create_dir(&root).unwrap(); - let target = dir.path().join("outside"); - std::fs::write(&target, "secret").unwrap(); - symlink(&target, root.join("link")).unwrap(); - - chown_sandbox_home( - &root, - Some(nix::unistd::geteuid()), - Some(nix::unistd::getegid()), - ) - .expect("symlink children should be skipped"); - } - - #[cfg(unix)] - #[test] - fn chown_recursive_skips_erofs_subtree_but_continues_siblings() { - use std::sync::{Arc, Mutex}; - - let dir = tempfile::tempdir().unwrap(); - let root = dir.path().join("sandbox"); - std::fs::create_dir(&root).unwrap(); - - let readonly_dir = root.join("ro-mount"); - std::fs::create_dir(&readonly_dir).unwrap(); - std::fs::write(readonly_dir.join("child-under-ro.txt"), "data").unwrap(); - std::fs::write(root.join("writable-sibling.txt"), "data").unwrap(); - - let chowned = Arc::new(Mutex::new(Vec::new())); - let observed = Arc::clone(&chowned); - let readonly_dir_for_chown = readonly_dir.clone(); - let fake_chown = - move |path: &Path, _uid: Option, _gid: Option| -> nix::Result<()> { - if path == readonly_dir_for_chown { - return Err(nix::errno::Errno::EROFS); - } - observed.lock().unwrap().push(path.to_path_buf()); - Ok(()) - }; - - chown_children( - &root, - Some(nix::unistd::geteuid()), - Some(nix::unistd::getegid()), - &fake_chown, - ) - .expect("read-only subtree should be skipped"); - - let chowned = chowned.lock().unwrap(); - assert!( - !chowned.contains(&readonly_dir.join("child-under-ro.txt")), - "children under EROFS directory must not be traversed" - ); - assert!( - chowned.contains(&root.join("writable-sibling.txt")), - "writable sibling should still be chowned" - ); - } - - #[cfg(unix)] - #[test] - fn chown_recursive_propagates_non_erofs_errors() { - let dir = tempfile::tempdir().unwrap(); - let root = dir.path().join("sandbox"); - std::fs::create_dir(&root).unwrap(); - let fake_chown = |_path: &Path, _uid: Option, _gid: Option| -> nix::Result<()> { - Err(nix::errno::Errno::EPERM) - }; + #[cfg(target_os = "linux")] + fn harden_child_process_marks_process_nondumpable() { + assert_eq!(probe_hardened_child(dumpable_flag_probe), 0); + } - let result = chown_recursive( - &root, - Some(nix::unistd::geteuid()), - Some(nix::unistd::getegid()), - &fake_chown, + #[test] + #[cfg(target_os = "linux")] + fn parse_pids_max_detects_limited_runtime() { + assert_eq!( + parse_pids_max("2048\n"), + RuntimePidLimitStatus::Limited(2048) ); - assert!(result.is_err(), "non-EROFS errors should propagate"); } - #[cfg(unix)] #[test] - fn prepare_oci_workspace_chowns_only_root() { - use std::sync::{Arc, Mutex}; - - let dir = tempfile::tempdir().unwrap(); - let root = dir.path().canonicalize().unwrap().join("sandbox"); - std::fs::create_dir(&root).unwrap(); - let child = root.join("image-content.txt"); - std::fs::write(&child, "image-owned").unwrap(); - - let chowned = Arc::new(Mutex::new(Vec::new())); - let observed = Arc::clone(&chowned); - let fake_chown = - move |path: &Path, _uid: Option, _gid: Option| -> nix::Result<()> { - observed.lock().unwrap().push(path.to_path_buf()); - Ok(()) - }; - - prepare_oci_workspace_with( - &root, - Some(nix::unistd::geteuid()), - Some(nix::unistd::getegid()), - &[], - &fake_chown, - ) - .expect("workspace root should be prepared"); - - assert_eq!(*chowned.lock().unwrap(), vec![root]); - assert!(child.exists(), "image-provided child should be untouched"); + #[cfg(target_os = "linux")] + fn parse_pids_max_detects_unlimited_runtime() { + assert_eq!(parse_pids_max("max\n"), RuntimePidLimitStatus::Unlimited); } - #[cfg(unix)] #[test] - fn validate_oci_workspace_accepts_existing_owner_writable_directory() { - let dir = tempfile::tempdir().unwrap(); - let root = dir.path().canonicalize().unwrap().join("project"); - std::fs::create_dir(&root).unwrap(); - std::fs::set_permissions(&root, std::fs::Permissions::from_mode(0o700)).unwrap(); - - validate_oci_workspace( - &root, - Some(nix::unistd::geteuid()), - Some(nix::unistd::getegid()), - &[], - ) - .expect("image owner already has write and traverse authority"); + #[cfg(target_os = "linux")] + fn parse_pids_max_reports_invalid_values() { + let status = parse_pids_max("not-a-number\n"); + assert!(matches!(status, RuntimePidLimitStatus::Unavailable(_))); } - #[cfg(unix)] #[test] - fn validate_oci_workspace_accepts_supplementary_group_write_authority() { - let dir = tempfile::tempdir_in("/tmp").unwrap(); - std::fs::set_permissions(dir.path(), std::fs::Permissions::from_mode(0o711)).unwrap(); - let root = dir.path().canonicalize().unwrap().join("project"); - std::fs::create_dir(&root).unwrap(); - std::fs::set_permissions(&root, std::fs::Permissions::from_mode(0o070)).unwrap(); - let metadata = std::fs::symlink_metadata(&root).unwrap(); - - validate_oci_workspace( - &root, - Some(Uid::from_raw(metadata.uid().wrapping_add(1))), - Some(Gid::from_raw(metadata.gid().wrapping_add(1))), - &[Gid::from_raw(metadata.gid())], - ) - .expect("supplementary group already has write and traverse authority"); + #[cfg(target_os = "linux")] + fn pid_limit_require_mode_rejects_missing_guardrail_statuses() { + for status in [ + RuntimePidLimitStatus::Unlimited, + RuntimePidLimitStatus::Unavailable("missing".to_string()), + ] { + let result = check_runtime_pid_limit_status(status, RuntimePidLimitMode::Require); + assert!(result.is_err()); + } } - #[cfg(unix)] #[test] - fn validate_oci_workspace_rejects_unwritable_directory() { - let dir = tempfile::tempdir_in("/tmp").unwrap(); - std::fs::set_permissions(dir.path(), std::fs::Permissions::from_mode(0o711)).unwrap(); - let root = dir.path().canonicalize().unwrap().join("project"); - std::fs::create_dir(&root).unwrap(); - std::fs::set_permissions(&root, std::fs::Permissions::from_mode(0o755)).unwrap(); - let metadata = std::fs::symlink_metadata(&root).unwrap(); - - let error = validate_oci_workspace( - &root, - Some(Uid::from_raw(metadata.uid().wrapping_add(1))), - Some(Gid::from_raw(metadata.gid().wrapping_add(1))), - &[], - ) - .unwrap_err(); - assert!(error.to_string().contains("not writable and traversable")); + #[cfg(target_os = "linux")] + fn pid_limit_warn_mode_accepts_missing_guardrail_statuses() { + for status in [ + RuntimePidLimitStatus::Unlimited, + RuntimePidLimitStatus::Unavailable("missing".to_string()), + ] { + let result = check_runtime_pid_limit_status(status, RuntimePidLimitMode::Warn); + assert!(result.is_ok()); + } } - #[cfg(unix)] - #[test] - fn validate_oci_workspace_rejects_missing_path() { - let dir = tempfile::tempdir().unwrap(); - let root = dir.path().canonicalize().unwrap().join("missing"); - - let error = validate_oci_workspace( - &root, - Some(nix::unistd::geteuid()), - Some(nix::unistd::getegid()), - &[], - ) - .unwrap_err(); - assert!(error.to_string().contains("does not exist")); + #[tokio::test] + async fn inject_provider_env_sets_placeholder_values() { + let mut cmd = Command::new("/usr/bin/env"); + cmd.stdin(StdStdio::null()) + .stdout(StdStdio::piped()) + .stderr(StdStdio::null()); + + let provider_env = std::iter::once(( + "ANTHROPIC_API_KEY".to_string(), + "openshell:resolve:env:ANTHROPIC_API_KEY".to_string(), + )) + .collect(); + + inject_provider_env(&mut cmd, &provider_env); + + let output = cmd.output().await.expect("spawn env"); + let stdout = String::from_utf8(output.stdout).expect("utf8"); + assert!(stdout.contains("ANTHROPIC_API_KEY=openshell:resolve:env:ANTHROPIC_API_KEY")); } #[cfg(target_os = "linux")] @@ -3301,405 +1513,6 @@ mod tests { } } - #[cfg(unix)] - #[test] - fn validate_oci_workspace_rejects_restrictive_parent() { - let dir = tempfile::tempdir().unwrap(); - let parent = dir.path().canonicalize().unwrap().join("private"); - let root = parent.join("project"); - std::fs::create_dir_all(&root).unwrap(); - std::fs::set_permissions(&parent, std::fs::Permissions::from_mode(0o700)).unwrap(); - std::fs::set_permissions(&root, std::fs::Permissions::from_mode(0o777)).unwrap(); - let metadata = std::fs::symlink_metadata(&parent).unwrap(); - - let error = validate_oci_workspace( - &root, - Some(Uid::from_raw(metadata.uid().wrapping_add(1))), - Some(Gid::from_raw(metadata.gid().wrapping_add(1))), - &[], - ) - .unwrap_err(); - assert!(error.to_string().contains("not traversable")); - } - - #[cfg(unix)] - #[test] - fn validate_oci_workspace_rejects_symlink_component() { - use std::os::unix::fs::symlink; - - let dir = tempfile::tempdir().unwrap(); - let base = dir.path().canonicalize().unwrap(); - let target = base.join("target"); - let link = base.join("link"); - std::fs::create_dir(&target).unwrap(); - symlink(&target, &link).unwrap(); - - let error = validate_oci_workspace( - &link, - Some(nix::unistd::geteuid()), - Some(nix::unistd::getegid()), - &[], - ) - .unwrap_err(); - assert!(error.to_string().contains("symlink")); - } - - #[cfg(unix)] - #[test] - fn prepare_oci_workspace_makes_existing_root_owner_writable() { - let dir = tempfile::tempdir().unwrap(); - let root = dir.path().canonicalize().unwrap().join("sandbox"); - std::fs::create_dir(&root).unwrap(); - std::fs::set_permissions(&root, std::fs::Permissions::from_mode(0o555)).unwrap(); - - prepare_oci_workspace_with(&root, None, None, &[], &|_, _, _| Ok(())) - .expect("read-only workspace root should be prepared"); - - let mode = std::fs::symlink_metadata(&root) - .unwrap() - .permissions() - .mode(); - assert_eq!(mode & 0o777, 0o755); - } - - #[cfg(unix)] - #[test] - fn prepare_oci_workspace_rejects_symlink_root() { - use std::os::unix::fs::symlink; - - let dir = tempfile::tempdir().unwrap(); - let base = dir.path().canonicalize().unwrap(); - let target = base.join("real"); - let link = base.join("link"); - std::fs::create_dir(&target).unwrap(); - symlink(&target, &link).unwrap(); - - let err = prepare_oci_workspace( - &link, - Some(nix::unistd::geteuid()), - Some(nix::unistd::getegid()), - &[], - ) - .unwrap_err(); - assert!( - err.to_string().contains("symlink"), - "expected symlink rejection: {err}" - ); - } - - #[cfg(unix)] - #[test] - fn prepare_oci_workspace_rejects_symlink_parent() { - use std::os::unix::fs::symlink; - - let dir = tempfile::tempdir().unwrap(); - let base = dir.path().canonicalize().unwrap(); - let target = base.join("real"); - let parent_link = base.join("parent-link"); - std::fs::create_dir(&target).unwrap(); - symlink(&target, &parent_link).unwrap(); - - let err = prepare_oci_workspace( - &parent_link.join("workspace"), - Some(nix::unistd::geteuid()), - Some(nix::unistd::getegid()), - &[], - ) - .unwrap_err(); - assert!( - err.to_string().contains("symlink"), - "expected parent symlink rejection: {err}" - ); - assert!( - !target.join("workspace").exists(), - "workspace must not be created through a symlink parent" - ); - } - - #[cfg(unix)] - #[test] - fn prepare_oci_workspace_rejects_parent_traversal() { - let err = prepare_oci_workspace( - Path::new("/tmp/workspace/../escape"), - Some(nix::unistd::geteuid()), - Some(nix::unistd::getegid()), - &[], - ) - .unwrap_err(); - assert!( - err.to_string().contains("must be normalized"), - "expected traversal rejection: {err}" - ); - } - - #[cfg(unix)] - #[test] - fn prepare_oci_workspace_rejects_inaccessible_existing_parent() { - let dir = tempfile::tempdir().unwrap(); - let parent = dir.path().canonicalize().unwrap().join("workspace"); - std::fs::create_dir(&parent).unwrap(); - std::fs::set_permissions(&parent, std::fs::Permissions::from_mode(0o700)).unwrap(); - let metadata = std::fs::symlink_metadata(&parent).unwrap(); - let different_user = Uid::from_raw(metadata.uid().wrapping_add(1)); - let different_group = Gid::from_raw(metadata.gid().wrapping_add(1)); - let root = parent.join("project"); - - let error = prepare_oci_workspace_with( - &root, - Some(different_user), - Some(different_group), - &[], - &|_, _, _| Ok(()), - ) - .unwrap_err(); - - assert!( - error.to_string().contains("is not traversable"), - "unexpected error: {error}" - ); - assert!( - !root.exists(), - "workspace must not be created below an inaccessible parent" - ); - } - - #[cfg(unix)] - #[test] - fn prepare_oci_workspace_accepts_supplementary_group_parent() { - let dir = tempfile::tempdir_in("/tmp").unwrap(); - std::fs::set_permissions(dir.path(), std::fs::Permissions::from_mode(0o710)).unwrap(); - let parent = dir.path().canonicalize().unwrap().join("workspace"); - std::fs::create_dir(&parent).unwrap(); - std::fs::set_permissions(&parent, std::fs::Permissions::from_mode(0o710)).unwrap(); - let metadata = std::fs::symlink_metadata(&parent).unwrap(); - let different_user = Uid::from_raw(metadata.uid().wrapping_add(1)); - let different_group = Gid::from_raw(metadata.gid().wrapping_add(1)); - let supplementary_group = Gid::from_raw(metadata.gid()); - let root = parent.join("project"); - - prepare_oci_workspace_with( - &root, - Some(different_user), - Some(different_group), - &[supplementary_group], - &|_, _, _| Ok(()), - ) - .expect("supplementary group execute permission should allow traversal"); - - assert!(root.is_dir()); - } - - #[cfg(not(any( - target_os = "aix", - target_os = "haiku", - target_os = "illumos", - target_os = "ios", - target_os = "macos", - target_os = "redox", - target_os = "solaris" - )))] - #[test] - fn named_user_supplementary_groups_include_primary_group() { - let user = User::from_uid(nix::unistd::geteuid()) - .expect("resolve current UID") - .expect("current user exists"); - - let groups = named_user_supplementary_groups(&user.name, user.gid) - .expect("resolve named-user supplementary groups"); - - assert!(groups.contains(&user.gid)); - } - - #[cfg(unix)] - #[test] - fn prepare_oci_workspace_rejects_non_directory_root() { - let dir = tempfile::tempdir().unwrap(); - let root = dir.path().canonicalize().unwrap().join("sandbox"); - std::fs::write(&root, "not a directory").unwrap(); - - let error = prepare_oci_workspace( - &root, - Some(nix::unistd::geteuid()), - Some(nix::unistd::getegid()), - &[], - ) - .unwrap_err(); - assert!( - error.to_string().contains("is not a directory"), - "unexpected error: {error}" - ); - } - - #[cfg(unix)] - #[test] - fn prepare_oci_workspace_propagates_root_chown_error() { - let dir = tempfile::tempdir().unwrap(); - let root = dir.path().canonicalize().unwrap().join("sandbox"); - std::fs::create_dir(&root).unwrap(); - let fake_chown = |_path: &Path, _uid: Option, _gid: Option| -> nix::Result<()> { - Err(nix::errno::Errno::EROFS) - }; - - let error = prepare_oci_workspace_with( - &root, - Some(nix::unistd::geteuid()), - Some(nix::unistd::getegid()), - &[], - &fake_chown, - ) - .unwrap_err(); - - assert!( - error.to_string().contains("Read-only file system"), - "unexpected error: {error}" - ); - } - - #[cfg(unix)] - #[test] - fn prepare_oci_workspace_creates_missing_root() { - use std::sync::{Arc, Mutex}; - - let dir = tempfile::tempdir().unwrap(); - let missing = dir - .path() - .canonicalize() - .unwrap() - .join("missing") - .join("sandbox"); - let chowned = Arc::new(Mutex::new(Vec::new())); - let observed = Arc::clone(&chowned); - let fake_chown = - move |path: &Path, _uid: Option, _gid: Option| -> nix::Result<()> { - observed.lock().unwrap().push(path.to_path_buf()); - Ok(()) - }; - - prepare_oci_workspace_with( - &missing, - Some(nix::unistd::geteuid()), - Some(nix::unistd::getegid()), - &[], - &fake_chown, - ) - .expect("missing OCI workspace should be created"); - - assert!(missing.is_dir()); - assert_eq!( - std::fs::symlink_metadata(missing.parent().unwrap()) - .unwrap() - .permissions() - .mode() - & 0o777, - 0o755 - ); - assert_eq!(*chowned.lock().unwrap(), vec![missing]); - } - - #[cfg(unix)] - #[test] - fn rewrite_passwd_modifies_existing_sandbox_entry() { - let dir = tempfile::tempdir().unwrap(); - let passwd = dir.path().join("passwd"); - std::fs::write( - &passwd, - "root:x:0:0:root:/root:/bin/bash\nsandbox:x:1000:1000::/sandbox:/bin/bash\n", - ) - .unwrap(); - - rewrite_passwd_at(&passwd, "5000", "6000").unwrap(); - - let content = std::fs::read_to_string(&passwd).unwrap(); - assert!(content.contains("sandbox:x:5000:6000::/sandbox:/bin/bash")); - assert!(content.contains("root:x:0:0:root:/root:/bin/bash")); - } - - #[cfg(unix)] - #[test] - fn rewrite_passwd_appends_when_no_sandbox_entry() { - let dir = tempfile::tempdir().unwrap(); - let passwd = dir.path().join("passwd"); - std::fs::write(&passwd, "root:x:0:0:root:/root:/bin/bash\n").unwrap(); - - rewrite_passwd_at(&passwd, "5000", "6000").unwrap(); - - let content = std::fs::read_to_string(&passwd).unwrap(); - assert!(content.contains("root:x:0:0:root:/root:/bin/bash")); - assert!(content.contains("sandbox:x:5000:6000::/sandbox:/bin/sh")); - } - - #[cfg(unix)] - #[test] - fn rewrite_group_modifies_existing_sandbox_entry() { - let dir = tempfile::tempdir().unwrap(); - let group = dir.path().join("group"); - std::fs::write(&group, "root:x:0:\nsandbox:x:1000:\n").unwrap(); - - rewrite_group_at(&group, "6000").unwrap(); - - let content = std::fs::read_to_string(&group).unwrap(); - assert!(content.contains("sandbox:x:6000:")); - assert!(content.contains("root:x:0:")); - } - - #[cfg(unix)] - #[test] - fn rewrite_group_appends_when_no_sandbox_entry() { - let dir = tempfile::tempdir().unwrap(); - let group = dir.path().join("group"); - std::fs::write(&group, "root:x:0:\n").unwrap(); - - rewrite_group_at(&group, "6000").unwrap(); - - let content = std::fs::read_to_string(&group).unwrap(); - assert!(content.contains("root:x:0:")); - assert!(content.contains("sandbox:x:6000:")); - } - - #[cfg(unix)] - #[test] - fn rewrite_passwd_leaves_malformed_entry_unchanged() { - let dir = tempfile::tempdir().unwrap(); - let passwd = dir.path().join("passwd"); - // Only 3 fields — slice pattern should fall through instead of panic. - std::fs::write(&passwd, "sandbox:x:1000\n").unwrap(); - rewrite_passwd_at(&passwd, "5000", "6000").unwrap(); - let content = std::fs::read_to_string(&passwd).unwrap(); - assert!(content.contains("sandbox:x:1000")); - } - - #[cfg(unix)] - #[test] - fn rewrite_group_leaves_malformed_entry_unchanged() { - let dir = tempfile::tempdir().unwrap(); - let group = dir.path().join("group"); - // Only 2 fields — slice pattern should fall through instead of panic. - std::fs::write(&group, "sandbox:x\n").unwrap(); - rewrite_group_at(&group, "6000").unwrap(); - let content = std::fs::read_to_string(&group).unwrap(); - assert!(content.contains("sandbox:x")); - } - - #[cfg(unix)] - #[test] - fn rewrite_passwd_preserves_other_entries() { - let dir = tempfile::tempdir().unwrap(); - let passwd = dir.path().join("passwd"); - std::fs::write( - &passwd, - "root:x:0:0:root:/root:/bin/bash\nnobody:x:65534:65534:nobody:/:/usr/sbin/nologin\nsandbox:x:1000:1000::/sandbox:/bin/bash\n", - ) - .unwrap(); - - rewrite_passwd_at(&passwd, "1234567", "1234567").unwrap(); - - let content = std::fs::read_to_string(&passwd).unwrap(); - assert!(content.contains("root:x:0:0:root:/root:/bin/bash")); - assert!(content.contains("nobody:x:65534:65534:nobody:/:/usr/sbin/nologin")); - assert!(content.contains("sandbox:x:1234567:1234567::/sandbox:/bin/bash")); - assert_eq!(content.lines().count(), 3); - } - #[tokio::test] async fn inject_provider_env_skips_supervisor_identity_material() { let mut cmd = Command::new("/usr/bin/env"); @@ -3789,122 +1602,4 @@ mod tests { } assert!(stdout.contains("PATH=/usr/bin:/bin")); } - - // ---- Numeric UID tests (Phase 2) ---- - - // Even a failing setuid(0) probe synchronizes libc credentials across all - // threads. Other tests own seccomp-notified launcher threads in this same - // process; signaling those while they await their broker can deadlock the - // parallel harness. Re-exec just the credential probe, without those threads. - fn numeric_uid_probe_runs_in_child(test_name: &str) -> bool { - const MARKER: &str = "OPENSHELL_TEST_ISOLATED_NUMERIC_UID_PROBE"; - if std::env::var(MARKER).as_deref() == Ok(test_name) { - return true; - } - let output = std::process::Command::new(std::env::current_exe().expect("test executable")) - .args(["--exact", test_name, "--test-threads=1", "--nocapture"]) - .env(MARKER, test_name) - .output() - .expect("run isolated credential probe"); - assert!( - output.status.success(), - "isolated credential probe failed: {}\n{}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - ); - false - } - - #[test] - fn drop_privileges_accepts_numeric_uid() { - if !numeric_uid_probe_runs_in_child("process::tests::drop_privileges_accepts_numeric_uid") { - return; - } - // When running as non-root, a numeric UID/GID that matches the - // current process should succeed without any passwd lookup. - if nix::unistd::geteuid().is_root() { - return; - } - - let uid_raw = nix::unistd::geteuid().as_raw(); - let gid_raw = nix::unistd::getegid().as_raw(); - - let policy = policy_with_process(ProcessPolicy { - run_as_user: Some(uid_raw.to_string()), - run_as_group: Some(gid_raw.to_string()), - }); - - assert!( - drop_privileges(&policy).is_ok(), - "should accept current process UID/GID as numeric strings" - ); - } - - #[test] - fn drop_privileges_numeric_uid_skips_initgroups() { - if !numeric_uid_probe_runs_in_child( - "process::tests::drop_privileges_numeric_uid_skips_initgroups", - ) { - return; - } - // When running as non-root with a numeric user but group matches, - // initgroups should not be called (guard: target_uid != geteuid()). - if nix::unistd::geteuid().is_root() { - return; - } - - let current_uid = nix::unistd::geteuid().as_raw(); - - // Use a different group name that exists (the current one). - let current_group = Group::from_gid(nix::unistd::getegid()) - .expect("should resolve current group") - .expect("current group should exist"); - - let policy = policy_with_process(ProcessPolicy { - run_as_user: Some(current_uid.to_string()), // numeric UID, no passwd entry needed - run_as_group: Some(current_group.name), // name-based group - }); - - assert!( - drop_privileges(&policy).is_ok(), - "should accept numeric UID with name-based group (initgroups guarded)" - ); - } - - #[test] - fn numeric_uid_privilege_drop_child() { - if std::env::var_os("OPENSHELL_TEST_NUMERIC_UID_CHILD").is_none() { - return; - } - let policy = policy_with_process(ProcessPolicy { - run_as_user: Some("999999".into()), - run_as_group: Some("999999".into()), - }); - match drop_privileges(&policy) { - Ok(()) => {} - Err(e) => { - assert!( - !e.to_string().contains("Failed to resolve user record"), - "unexpected error for numeric UID without passwd entry: {e}" - ); - } - } - } - - #[test] - fn drop_privileges_numeric_uid_without_passwd_entry_skips_lookup() { - let mut cmd = std::process::Command::new(std::env::current_exe().expect("current exe")); - cmd.arg("numeric_uid_privilege_drop_child") - .arg("--nocapture") - .env("OPENSHELL_TEST_NUMERIC_UID_CHILD", "1") - .stdin(StdStdio::null()) - .stdout(StdStdio::piped()) - .stderr(StdStdio::piped()); - let output = cmd.output().expect("spawn child"); - assert!( - output.status.success(), - "numeric UID privilege drop child failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - } } diff --git a/crates/openshell-sandbox/src/sandbox/linux/landlock.rs b/crates/openshell-sandbox/src/sandbox/linux/landlock.rs index c18b633e75..486495c956 100644 --- a/crates/openshell-sandbox/src/sandbox/linux/landlock.rs +++ b/crates/openshell-sandbox/src/sandbox/linux/landlock.rs @@ -88,9 +88,10 @@ pub fn probe_availability() -> LandlockAvailability { /// A prepared Landlock ruleset ready to be enforced via `restrict_self()`. /// -/// Created by [`prepare`] while running as root (so `PathFd::new()` can open -/// any path regardless of DAC permissions). Enforced by [`enforce`] after -/// `drop_privileges()` — `restrict_self()` does not require elevated privileges. +/// Path FDs are opened before enforcement. The capability-free launch path +/// prepares the baseline and user rules as the workload identity, then calls +/// [`enforce`] in the child before exec. `restrict_self()` does not require +/// elevated privileges. pub struct PreparedRuleset { ruleset: landlock::RulesetCreated, compatibility: LandlockCompatibility, @@ -102,10 +103,11 @@ enum PathOpenMode { CurrentUser, } -/// Phase 1: Open `PathFds` and build the Landlock ruleset **as root**. +/// Phase 1: Open `PathFds` and build the Landlock ruleset with strict path opening. /// -/// This must run before `drop_privileges()` so that `PathFd::new()` can open -/// paths that are only accessible to root (e.g. mode 700 directories). +/// Opens configured paths as the calling identity. Inaccessible paths fail in +/// hard-requirement mode and are skipped in best-effort mode. Unlike +/// [`prepare_current_user`], this does not always omit inaccessible paths. /// /// Returns `None` if there are no filesystem paths to restrict (no-op). /// Returns `Some(PreparedRuleset)` on success, or an error. @@ -394,9 +396,9 @@ fn prepare_with_path_open_mode( /// Phase 2: Enforce a prepared Landlock ruleset by calling `restrict_self()`. /// -/// This runs **after** `drop_privileges()`. The `restrict_self()` syscall does -/// not require root — it only restricts the calling thread (and its future -/// children), which is always permitted. +/// The capability-free launch path calls this in the child before exec, already +/// running as the workload identity. `restrict_self()` does not require root; +/// it restricts the calling thread and its future children. /// /// Respects the same `best_effort` / `hard_requirement` compatibility as /// [`prepare`]: if `restrict_self()` fails and the policy is `best_effort`, diff --git a/crates/openshell-sandbox/src/sandbox/linux/mod.rs b/crates/openshell-sandbox/src/sandbox/linux/mod.rs index 3f0084450e..acfd46edc5 100644 --- a/crates/openshell-sandbox/src/sandbox/linux/mod.rs +++ b/crates/openshell-sandbox/src/sandbox/linux/mod.rs @@ -18,10 +18,11 @@ pub struct PreparedSandbox { policy: SandboxPolicy, } -/// Phase 1: Prepare sandbox restrictions **as root** (before `drop_privileges`). +/// Phase 1: Prepare sandbox restrictions with strict path opening. /// -/// Opens Landlock `PathFds` while the process still has root privileges, -/// ensuring paths like mode-700 directories are accessible. +/// Opens configured paths as the calling identity and handles failures according +/// to the policy's Landlock compatibility mode. +/// The capability-free launch path uses [`prepare_capability_free`] instead. pub fn prepare(policy: &SandboxPolicy, workdir: Option<&str>) -> Result { let landlock = landlock::prepare(policy, workdir)?; Ok(PreparedSandbox { @@ -66,7 +67,7 @@ pub fn prepare_capability_free( }) } -/// Phase 2: Enforce prepared sandbox restrictions (after `drop_privileges`). +/// Phase 2: Enforce prepared sandbox restrictions in the child before exec. /// /// Calls `restrict_self()` for Landlock and applies seccomp filters. /// Neither operation requires root privileges. From 9912d21d30978d9a4389a71e871da47e0f974feb Mon Sep 17 00:00:00 2001 From: krishicks Date: Wed, 30 Sep 2026 23:06:03 +0000 Subject: [PATCH 11/33] fix(e2e): keep locally built Kubernetes images off the chart's default registry (#3991) 679b19067 added global.image.registry (ghcr.io/nvidia) as the fallback for empty per-image registries and split e2e image references into registry and repository. Locally built images such as openshell/gateway: have no registry host, so the chart rewrote them to ghcr.io/nvidia/openshell/* and the k3d cluster could not pull them. Clear global.image.registry in the Kubernetes e2e wrapper, which sets every image's registry explicitly. Signed-off-by: Kris Hicks --- e2e/with-kube-gateway.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/e2e/with-kube-gateway.sh b/e2e/with-kube-gateway.sh index 5d00e11adb..c4ace7ae89 100755 --- a/e2e/with-kube-gateway.sh +++ b/e2e/with-kube-gateway.sh @@ -652,6 +652,7 @@ run_scenario() { --namespace "${NAMESPACE}" --create-namespace \ "${helm_values_args[@]}" \ --set "fullnameOverride=openshell" \ + "${GLOBAL_HELM_IMAGE_ARGS[@]}" \ "${GATEWAY_HELM_IMAGE_ARGS[@]}" \ "${SUPERVISOR_HELM_IMAGE_ARGS[@]}" \ "${SANDBOX_RUNTIME_HELM_IMAGE_ARGS[@]}" \ @@ -942,6 +943,9 @@ SUPERVISOR_IMAGE="$(e2e_resolve_image_reference "${SUPERVISOR_IMAGE:-${REGISTRY_ SANDBOX_RUNTIME_IMAGE="$(e2e_resolve_image_reference "${SANDBOX_IMAGE:-${REGISTRY_VALUE}/sandbox}" "${IMAGE_TAG_VALUE}")" BUILD_GATEWAY_IMAGE="${REGISTRY_VALUE}/gateway:${IMAGE_TAG_VALUE}" BUILD_SUPERVISOR_IMAGE="${REGISTRY_VALUE}/supervisor:${IMAGE_TAG_VALUE}" +# Each image carries its own registry; clear the chart's default so a +# registry-less local tag is not rewritten to ghcr.io. +GLOBAL_HELM_IMAGE_ARGS=(--set-string "global.image.registry=") GATEWAY_HELM_IMAGE_ARGS=(--set-string "gateway.image.registry=$(e2e_image_reference_registry "${GATEWAY_IMAGE}")" --set-string "gateway.image.repository=$(e2e_image_reference_repository_path "${GATEWAY_IMAGE}")" --set-string "gateway.image.tag=$(e2e_image_reference_tag "${GATEWAY_IMAGE}")" --set-string "gateway.image.digest=$(e2e_image_reference_digest "${GATEWAY_IMAGE}")") SUPERVISOR_HELM_IMAGE_ARGS=(--set-string "supervisor.image.registry=$(e2e_image_reference_registry "${SUPERVISOR_IMAGE}")" --set-string "supervisor.image.repository=$(e2e_image_reference_repository_path "${SUPERVISOR_IMAGE}")" --set-string "supervisor.image.tag=$(e2e_image_reference_tag "${SUPERVISOR_IMAGE}")" --set-string "supervisor.image.digest=$(e2e_image_reference_digest "${SUPERVISOR_IMAGE}")") SANDBOX_RUNTIME_HELM_IMAGE_ARGS=(--set-string "sandboxRuntime.image.registry=$(e2e_image_reference_registry "${SANDBOX_RUNTIME_IMAGE}")" --set-string "sandboxRuntime.image.repository=$(e2e_image_reference_repository_path "${SANDBOX_RUNTIME_IMAGE}")" --set-string "sandboxRuntime.image.tag=$(e2e_image_reference_tag "${SANDBOX_RUNTIME_IMAGE}")" --set-string "sandboxRuntime.image.digest=$(e2e_image_reference_digest "${SANDBOX_RUNTIME_IMAGE}")") @@ -1384,6 +1388,7 @@ else --namespace "${NAMESPACE}" --create-namespace \ "${helm_values_args[@]}" \ --set "fullnameOverride=openshell" \ + "${GLOBAL_HELM_IMAGE_ARGS[@]}" \ "${GATEWAY_HELM_IMAGE_ARGS[@]}" \ "${SUPERVISOR_HELM_IMAGE_ARGS[@]}" \ "${SANDBOX_RUNTIME_HELM_IMAGE_ARGS[@]}" \ From 5a91572be74466985a3b2892c487899dc4bdf173 Mon Sep 17 00:00:00 2001 From: Jim Meyer Date: Thu, 1 Oct 2026 00:02:25 +0000 Subject: [PATCH 12/33] fix(gator): require full head SHA for /ok to test (#4007) * fix(gator): require full head SHA for /ok to test copy-pr-bot will stop accepting abbreviated SHAs in /ok to test comments. Tell gator to read the full 40-character head SHA immediately before posting, and make the gh wrapper reject any /ok to test comment that is not exactly the command with the current full head SHA. Signed-off-by: Jim Meyer * fix(gator): drop gh wrapper /ok to test guard Keep the change to the gator-gate skill instructions only. Signed-off-by: Jim Meyer * fix(gator): unify /ok to test SHA placeholder Use for every /ok to test reference in the gator-gate skill and state the full-SHA requirement directly. Signed-off-by: Jim Meyer --------- Signed-off-by: Jim Meyer --- .agents/skills/launch-openshell-gator/SKILL.md | 2 +- scripts/agents/gator/skills/gator-gate/SKILL.md | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.agents/skills/launch-openshell-gator/SKILL.md b/.agents/skills/launch-openshell-gator/SKILL.md index 80a84ca744..7e3229d9e3 100644 --- a/.agents/skills/launch-openshell-gator/SKILL.md +++ b/.agents/skills/launch-openshell-gator/SKILL.md @@ -217,7 +217,7 @@ sandbox_name="gator-pr-${pr_number}-supervised" --gateway "$gateway_name" \ --name "$sandbox_name" \ --watch \ - "Review and monitor PR #${pr_number} through the gator-gate workflow. Scope this invocation only to PR #${pr_number}. The operator explicitly authorizes applying the test:e2e label, posting /ok to test for the current head SHA, and rerunning the relevant current-head workflow when the E2E Label Help bot says that is required." + "Review and monitor PR #${pr_number} through the gator-gate workflow. Scope this invocation only to PR #${pr_number}. The operator explicitly authorizes applying the test:e2e label, posting /ok to test with the full 40-character current head SHA, and rerunning the relevant current-head workflow when the E2E Label Help bot says that is required." ``` ## Model Or Image Experiments diff --git a/scripts/agents/gator/skills/gator-gate/SKILL.md b/scripts/agents/gator/skills/gator-gate/SKILL.md index 494946286d..218fd6f3d1 100644 --- a/scripts/agents/gator/skills/gator-gate/SKILL.md +++ b/scripts/agents/gator/skills/gator-gate/SKILL.md @@ -38,7 +38,7 @@ If the `principal-engineer-reviewer` sub-agent fails before producing usable rev - Do not push commits to a contributor's PR branch by default. - You may push changes only when explicitly instructed by a GitHub comment from a maintainer or by a direct operator prompt. -- Do not post `/ok to test ` unless the current GitHub user has maintainer authority. +- Do not post `/ok to test ` unless the current GitHub user has maintainer authority. - Code review is code-only. Do not run pre-commit, unit tests, or E2E locally as part of the initial PR review unless explicitly instructed. - Security vulnerabilities must not be triaged through public GitHub issues. Follow `SECURITY.md`. @@ -440,7 +440,7 @@ Move to `gator:blocked` when any of these apply: - PR is blocked by the vouch system or was auto-closed for lack of vouch - DCO is missing or failing - PR has merge conflicts or `mergeStateStatus` indicates dirty/blocked for conflict reasons -- Required `/ok to test ` is needed and the current user lacks maintainer authority +- Required `/ok to test ` is needed and the current user lacks maintainer authority - Required CI cannot run because the copy-pr mirror is missing or stale and maintainer authority is unavailable For auto-closed vouch-gate PRs, do not treat the proposal as invalid. Comment only if useful, then stop and wait until the author is vouched and the PR is reopened. @@ -929,12 +929,18 @@ After applying a `test:*` label, read the bot comment that is posted by the E2E If a mirror is missing or stale and you have maintainer authority, post: ```text -/ok to test +/ok to test ``` -The `/ok to test ` comment must contain only that command. Do not include the `> **gator-agent**` marker, explanations, Markdown fences, or any other text in the same comment. +`` MUST be the full 40-character SHA-1 of the current PR head. Read it fresh from GitHub immediately before posting instead of reusing a SHA from earlier in the cycle: -If you do not have maintainer authority, move to `gator:blocked` and state that a maintainer must post `/ok to test `. +```bash +gh api repos/NVIDIA/OpenShell/pulls/ --jq .head.sha +``` + +The `/ok to test ` comment must contain only that command. Do not include the `> **gator-agent**` marker, explanations, Markdown fences, or any other text in the same comment. + +If you do not have maintainer authority, move to `gator:blocked` and state that a maintainer must post `/ok to test ` with the full current head SHA written out. Do not treat a test label or `/ok to test` comment as proof that testing started. Confirm that every required workflow has a check or run for the From 2935e9731b97e89ae78aa7bc66867bc2281444ef Mon Sep 17 00:00:00 2001 From: "John T. Myers" <9696606+johntmyers@users.noreply.github.com> Date: Thu, 1 Oct 2026 00:09:08 +0000 Subject: [PATCH 13/33] fix(gateway): delete finalized ephemeral sandboxes while connected (#3984) Start driver cleanup after terminal finalization and retain disconnect fallback. Add detached success and failure e2e coverage across supervisor-based drivers. Closes #3938 Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> --- .../skills/launch-openshell-gator/SKILL.md | 2 +- crates/openshell-server/src/compute/mod.rs | 118 ++++++++++- .../src/supervisor_session.rs | 14 +- e2e/rust/Cargo.toml | 5 + e2e/rust/e2e-podman.sh | 1 + e2e/rust/e2e-vm.sh | 1 + e2e/rust/tests/ephemeral_cleanup.rs | 189 ++++++++++++++++++ 7 files changed, 322 insertions(+), 8 deletions(-) create mode 100644 e2e/rust/tests/ephemeral_cleanup.rs diff --git a/.agents/skills/launch-openshell-gator/SKILL.md b/.agents/skills/launch-openshell-gator/SKILL.md index 7e3229d9e3..2f9680d411 100644 --- a/.agents/skills/launch-openshell-gator/SKILL.md +++ b/.agents/skills/launch-openshell-gator/SKILL.md @@ -158,7 +158,7 @@ sandbox_name="gator-pr-${pr_number}-supervised" "Review and monitor PR #${pr_number} through the gator-gate workflow. Scope this invocation only to PR #${pr_number}." ``` -The launcher queries the gateway's selected compute driver, builds the gator image in the matching Docker or Podman image store, stages the immutable payload, imports provider profiles, configures provider credentials and refresh, and starts the agent supervisor as the sandbox's canonical main process. The detached main process survives loss of the host CLI connection and reconnects to a restarted gateway. Unless `--keep` is set, the sandbox is marked ephemeral so the gateway deletes it after the supervisor exits. `CONTAINER_ENGINE`, when set, must match the gateway driver. +The launcher queries the gateway's selected compute driver, builds the gator image in the matching Docker or Podman image store, stages the immutable payload, imports provider profiles, configures provider credentials and refresh, and starts the agent supervisor as the sandbox's canonical main process. The detached main process survives loss of the host CLI connection and reconnects to a restarted gateway. Unless `--keep` is set, the sandbox is marked ephemeral so the gateway deletes it after the canonical main process exits and its terminal result is finalized. `CONTAINER_ENGINE`, when set, must match the gateway driver. The launcher streams image-build and provisioning output until the detached workload is ready, then exits. Use `openshell logs ` or the TUI for runtime output. diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index d2e9642263..264cd77bbb 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -2135,6 +2135,7 @@ impl ComputeRuntime { } } + #[cfg(test)] pub(crate) async fn delete_sandbox( &self, workspace: &str, @@ -4725,6 +4726,39 @@ impl ComputeRuntime { Ok(()) } + /// Start ephemeral cleanup only after the finalize RPC has recorded the + /// terminal result and marked its supervisor session finalized. + pub async fn cleanup_finalized_ephemeral_sandbox( + &self, + sandbox_id: &str, + instance_id: &str, + ) -> Result<(), String> { + let _guard = self.sync_lock.lock().await; + let Some(sandbox) = self + .store + .get_message::(sandbox_id) + .await + .map_err(|error| error.to_string())? + else { + return Ok(()); + }; + let phase = SandboxPhase::try_from(sandbox.phase()).unwrap_or(SandboxPhase::Unknown); + if phase != SandboxPhase::Completed && !is_failed_main_process_result(&sandbox) { + return Ok(()); + } + let Some(status) = sandbox.status.as_ref() else { + return Ok(()); + }; + if status.exit_code.is_none() + || (!status.main_process_instance_id.is_empty() + && status.main_process_instance_id != instance_id) + { + return Ok(()); + } + self.schedule_ephemeral_sandbox_delete(&sandbox); + Ok(()) + } + fn schedule_ephemeral_sandbox_delete(&self, sandbox: &Sandbox) { if provisioning_deadline::timed_out(sandbox) { return; @@ -4740,10 +4774,10 @@ impl ComputeRuntime { } let runtime = self.clone(); - let workspace = sandbox.object_workspace().to_string(); + let sandbox_id = sandbox.object_id().to_string(); let name = sandbox.object_name().to_string(); tokio::spawn(async move { - if let Err(error) = runtime.delete_sandbox(&workspace, &name).await { + if let Err(error) = runtime.delete_sandbox_by_id(&sandbox_id, &name).await { tracing::warn!( sandbox_name = %name, error = %error, @@ -9300,7 +9334,83 @@ mod tests { .unwrap(); assert_eq!(driver.delete_calls(), 0); runtime - .supervisor_session_disconnected("sb-1", true) + .cleanup_finalized_ephemeral_sandbox("sb-1", "instance-1") + .await + .unwrap(); + tokio::time::timeout(Duration::from_secs(1), async { + while driver.delete_calls() == 0 { + tokio::task::yield_now().await; + } + }) + .await + .expect("terminal finalization should delete before the supervisor disconnects"); + } + + #[tokio::test] + async fn finalized_ephemeral_cleanup_skips_retained_and_restarting_sandboxes() { + for (retention, restart_policy, exit_code) in [ + (None, SandboxRestartPolicy::Never, 0), + (Some("ephemeral"), SandboxRestartPolicy::OnFailure, 9), + ] { + let driver = ControlledDriver::new(); + let runtime = test_runtime(driver.clone()).await; + let mut sandbox = sandbox_record("sb-1", "sandbox-a", SandboxPhase::Provisioning); + if let Some(retention) = retention { + sandbox.metadata.as_mut().unwrap().annotations.insert( + "openshell.nvidia.com/retention".to_string(), + retention.to_string(), + ); + } + sandbox.spec = Some(SandboxSpec { + restart_policy: restart_policy as i32, + ..Default::default() + }); + runtime.store.put_message(&sandbox).await.unwrap(); + runtime + .supervisor_session_connected("sb-1", "instance-1") + .await + .unwrap(); + runtime + .report_main_process_exit("sb-1", "instance-1", exit_code) + .await + .unwrap(); + runtime + .finalize_main_process_exit("sb-1", "instance-1") + .await + .unwrap(); + runtime + .cleanup_finalized_ephemeral_sandbox("sb-1", "instance-1") + .await + .unwrap(); + tokio::task::yield_now().await; + assert_eq!(driver.delete_calls(), 0); + } + } + + #[tokio::test] + async fn finalized_failed_ephemeral_sandbox_deletes_while_connected() { + let driver = ControlledDriver::new(); + let runtime = test_runtime(driver.clone()).await; + let mut sandbox = sandbox_record("sb-1", "sandbox-a", SandboxPhase::Provisioning); + sandbox.metadata.as_mut().unwrap().annotations.insert( + "openshell.nvidia.com/retention".to_string(), + "ephemeral".to_string(), + ); + runtime.store.put_message(&sandbox).await.unwrap(); + runtime + .supervisor_session_connected("sb-1", "instance-1") + .await + .unwrap(); + runtime + .report_main_process_exit("sb-1", "instance-1", 9) + .await + .unwrap(); + runtime + .finalize_main_process_exit("sb-1", "instance-1") + .await + .unwrap(); + runtime + .cleanup_finalized_ephemeral_sandbox("sb-1", "instance-1") .await .unwrap(); tokio::time::timeout(Duration::from_secs(1), async { @@ -9309,7 +9419,7 @@ mod tests { } }) .await - .expect("terminal finalization should release ephemeral cleanup"); + .expect("failed canonical main should delete its ephemeral sandbox"); } #[tokio::test] diff --git a/crates/openshell-server/src/supervisor_session.rs b/crates/openshell-server/src/supervisor_session.rs index 710ecc1406..847d625a28 100644 --- a/crates/openshell-server/src/supervisor_session.rs +++ b/crates/openshell-server/src/supervisor_session.rs @@ -2072,10 +2072,18 @@ pub async fn handle_finalize_main_process_exit( .finalize_main_process_exit(&report.sandbox_id, &report.instance_id) .await .map_err(Status::failed_precondition)?; - if !state + let session_finalized = state .supervisor_sessions - .finalize_main_process_exit(&report.sandbox_id) - { + .finalize_main_process_exit(&report.sandbox_id); + // The session can close between durable result validation and this mark. + // Schedule cleanup in either case so a disconnect with an unfinalized + // in-memory session cannot strand the ephemeral sandbox. + state + .compute + .cleanup_finalized_ephemeral_sandbox(&report.sandbox_id, &report.instance_id) + .await + .map_err(Status::internal)?; + if !session_finalized { return Err(Status::failed_precondition( "supervisor session is not connected", )); diff --git a/e2e/rust/Cargo.toml b/e2e/rust/Cargo.toml index a2a552400f..6b1fc73723 100644 --- a/e2e/rust/Cargo.toml +++ b/e2e/rust/Cargo.toml @@ -98,6 +98,11 @@ name = "local_driver_token_restart" path = "tests/local_driver_token_restart.rs" required-features = ["e2e"] +[[test]] +name = "ephemeral_cleanup" +path = "tests/ephemeral_cleanup.rs" +required-features = ["e2e"] + [[test]] name = "podman_gateway_start" path = "tests/podman_gateway_start.rs" diff --git a/e2e/rust/e2e-podman.sh b/e2e/rust/e2e-podman.sh index bbbcfe6fa9..c0deb2c750 100755 --- a/e2e/rust/e2e-podman.sh +++ b/e2e/rust/e2e-podman.sh @@ -22,6 +22,7 @@ source "${ROOT}/e2e/support/conformance.sh" # stabilized and can be added here. PODMAN_CI_TESTS=( bypass_detection + ephemeral_cleanup core_dump_hardening credential_gating default_image diff --git a/e2e/rust/e2e-vm.sh b/e2e/rust/e2e-vm.sh index 6fd355170b..a30f5283d3 100755 --- a/e2e/rust/e2e-vm.sh +++ b/e2e/rust/e2e-vm.sh @@ -411,6 +411,7 @@ run_e2e_test() { if [ -n "${E2E_TEST_OVERRIDE}" ]; then run_e2e_test "${E2E_TEST_OVERRIDE}" else + run_e2e_test ephemeral_cleanup run_e2e_test host_gateway_alias run_e2e_test vm_overlay run_e2e_test vm_gateway_start diff --git a/e2e/rust/tests/ephemeral_cleanup.rs b/e2e/rust/tests/ephemeral_cleanup.rs new file mode 100644 index 0000000000..eca4bb1d68 --- /dev/null +++ b/e2e/rust/tests/ephemeral_cleanup.rs @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Detached ephemeral lifecycle coverage shared by supervisor-based drivers. + +#![cfg(feature = "e2e")] + +use std::path::PathBuf; +use std::process::{Command, Stdio}; +use std::time::Duration; + +use openshell_e2e::harness::binary::{openshell_bin, openshell_cmd}; +use openshell_e2e::harness::cli::run_cli; +use openshell_e2e::harness::container::{ContainerEngine, e2e_driver}; +use openshell_e2e::harness::sandbox::{E2E_WORKLOAD_IMAGE, unique_sandbox_name}; +use serial_test::serial; +use tokio::time::{Instant, sleep}; + +const CLEANUP_TIMEOUT: Duration = Duration::from_secs(90); + +struct DeleteOnFailure { + name: String, + armed: bool, +} + +impl Drop for DeleteOnFailure { + fn drop(&mut self) { + if self.armed { + let _ = Command::new(openshell_bin()) + .args(["sandbox", "delete", &self.name]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + } + } +} + +fn command_output(mut command: Command) -> Result { + let output = command + .output() + .map_err(|error| format!("run driver resource query: {error}"))?; + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + if !output.status.success() { + return Err(format!( + "driver resource query failed ({}): {stdout}{stderr}", + output.status + )); + } + Ok(stdout.trim().to_string()) +} + +fn driver_resources_present(sandbox_id: &str) -> Result { + match e2e_driver().as_deref() { + Some("docker" | "podman") => { + let engine = ContainerEngine::from_env()?; + let mut command = engine.command(); + command.args([ + "ps", + "-aq", + "--filter", + &format!("label=openshell.ai/sandbox-id={sandbox_id}"), + ]); + Ok(!command_output(command)?.is_empty()) + } + Some("kubernetes") => { + let mut command = Command::new("kubectl"); + command.args([ + "get", + "pods,sandboxes.agents.x-k8s.io", + "--all-namespaces", + "--selector", + &format!("openshell.ai/sandbox-id={sandbox_id}"), + "--output=name", + ]); + Ok(!command_output(command)?.is_empty()) + } + Some("vm") => { + let state_dir = std::env::var_os("OPENSHELL_E2E_VM_STATE_DIR") + .map(PathBuf::from) + .ok_or("OPENSHELL_E2E_VM_STATE_DIR must be set for VM resource checks")?; + Ok(state_dir.join("sandboxes").join(sandbox_id).exists()) + } + other => Err(format!( + "unsupported e2e driver for ephemeral cleanup: {other:?}" + )), + } +} + +async fn run_detached_ephemeral_cleanup(exit_code: i32) -> Result<(), String> { + let name = unique_sandbox_name(); + let mut cleanup = DeleteOnFailure { + name: name.clone(), + armed: true, + }; + let release_path = format!("/sandbox/.ephemeral-release-{name}"); + let script = + format!("while [ ! -e '{release_path}' ]; do sleep 0.1; done; sleep 1; exit {exit_code}"); + let mut create = openshell_cmd(); + create.args([ + "sandbox", + "create", + "--name", + &name, + "--from", + E2E_WORKLOAD_IMAGE, + "--no-keep", + "--detach", + "--", + "sh", + "-c", + &script, + ]); + let created = create + .output() + .await + .map_err(|error| format!("create detached ephemeral sandbox: {error}"))?; + if !created.status.success() { + return Err(format!( + "create detached ephemeral sandbox failed ({}): {}{}", + created.status, + String::from_utf8_lossy(&created.stdout), + String::from_utf8_lossy(&created.stderr) + )); + } + + let (details, get_code) = run_cli(&["sandbox", "get", &name, "--output", "json"]).await; + if get_code != 0 { + return Err(format!("get detached sandbox failed: {details}")); + } + let details: serde_json::Value = + serde_json::from_str(&details).map_err(|error| format!("parse sandbox JSON: {error}"))?; + let sandbox_id = details["id"] + .as_str() + .ok_or_else(|| format!("sandbox has no id: {details}"))?; + if !driver_resources_present(sandbox_id)? { + return Err(format!( + "driver resources were never observed for sandbox {name} ({sandbox_id})" + )); + } + + let (release, release_code) = run_cli(&[ + "sandbox", + "exec", + "--name", + &name, + "--no-tty", + "--no-login-shell", + "--", + "touch", + &release_path, + ]) + .await; + if release_code != 0 { + return Err(format!("release canonical process failed: {release}")); + } + + let deadline = Instant::now() + CLEANUP_TIMEOUT; + loop { + let (names, list_code) = run_cli(&["sandbox", "list", "--names"]).await; + if list_code != 0 { + return Err(format!("list sandboxes failed: {names}")); + } + let record_present = names.lines().any(|line| line.trim() == name); + let resources_present = driver_resources_present(sandbox_id)?; + if !record_present && !resources_present { + cleanup.armed = false; + return Ok(()); + } + if Instant::now() >= deadline { + return Err(format!( + "ephemeral sandbox {name} ({sandbox_id}) remained after {CLEANUP_TIMEOUT:?}: record_present={record_present}, resources_present={resources_present}" + )); + } + sleep(Duration::from_millis(500)).await; + } +} + +#[tokio::test] +#[serial(ephemeral_cleanup)] +async fn detached_ephemeral_success_removes_sandbox_and_driver_resources() { + run_detached_ephemeral_cleanup(0).await.unwrap(); +} + +#[tokio::test] +#[serial(ephemeral_cleanup)] +async fn detached_ephemeral_failure_removes_sandbox_and_driver_resources() { + run_detached_ephemeral_cleanup(17).await.unwrap(); +} From 021400be8af471f8669369e679de3e18cf0bd672 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Thu, 1 Oct 2026 04:33:25 +0000 Subject: [PATCH 14/33] refactor(auth): separate sandbox identity from TLS (#3110) * refactor(auth): separate sandbox identity from TLS Signed-off-by: Drew Newberry * docs(auth): clarify gateway mTLS behavior Signed-off-by: Drew Newberry * test(auth): include workspace scope in TLS authorization checks Signed-off-by: Drew Newberry * test(e2e): bound service auth sandbox names for large PIDs Signed-off-by: Drew Newberry --------- Signed-off-by: Drew Newberry --- .agents/skills/helm-dev-environment/SKILL.md | 5 +- crates/openshell-bootstrap/src/pki.rs | 2 +- crates/openshell-core/src/config.rs | 6 +- crates/openshell-core/src/container_paths.rs | 8 - crates/openshell-core/src/driver_utils.rs | 17 +- crates/openshell-core/src/grpc_client.rs | 29 +-- crates/openshell-core/src/sandbox_env.rs | 2 +- crates/openshell-driver-docker/README.md | 6 +- crates/openshell-driver-docker/src/lib.rs | 66 ++----- crates/openshell-driver-docker/src/tests.rs | 13 +- crates/openshell-driver-kubernetes/README.md | 5 +- .../openshell-driver-kubernetes/src/config.rs | 2 +- .../openshell-driver-kubernetes/src/driver.rs | 10 +- .../src/sandbox_runtime.rs | 111 ++++++----- crates/openshell-driver-podman/README.md | 5 +- crates/openshell-driver-podman/src/config.rs | 132 +++---------- .../openshell-driver-podman/src/container.rs | 99 ++-------- crates/openshell-driver-podman/src/driver.rs | 30 +-- crates/openshell-driver-podman/src/main.rs | 6 +- crates/openshell-driver-vm/README.md | 2 - crates/openshell-driver-vm/src/driver.rs | 43 ++--- crates/openshell-driver-vm/src/main.rs | 2 + crates/openshell-gateway/src/lib.rs | 62 ++---- crates/openshell-gateway/src/vm.rs | 69 +------ crates/openshell-server/src/auth/principal.rs | 3 - crates/openshell-server/src/cli.rs | 115 ++++-------- .../src/compute/driver_config.rs | 176 ++++-------------- crates/openshell-server/src/config_file.rs | 4 - crates/openshell-server/src/lib.rs | 33 +--- crates/openshell-server/src/multiplex.rs | 6 +- deploy/rpm/CONFIGURATION.md | 25 +-- deploy/rpm/QUICKSTART.md | 5 +- deploy/rpm/TROUBLESHOOTING.md | 16 +- docs/about/architecture.mdx | 4 + docs/how-it-works/gateways/authentication.mdx | 12 +- docs/how-it-works/gateways/configuration.mdx | 45 ++--- docs/how-it-works/sandboxes/runtimes.mdx | 9 +- docs/kubernetes/access-control.mdx | 9 +- docs/kubernetes/managing-certificates.mdx | 2 +- docs/security/best-practices.mdx | 10 +- .../gateway/schema-v2-capability-parity.toml | 16 +- .../schema-v2-intentional-changes.toml | 6 +- e2e/parity/test.sh | 3 + e2e/parity/verify-results.py | 16 +- e2e/python/test_security_tls.py | 57 ++++-- e2e/rust/e2e-vm.sh | 4 - e2e/rust/tests/service_bearer_passthrough.rs | 2 +- e2e/support/podman-gateway-config.sh | 2 - e2e/with-docker-gateway.sh | 4 - e2e/with-podman-gateway.sh | 28 ++- ...chema_v2_compute_boundary_verifier_test.py | 21 ++- rfc/0003-gateway-configuration/README.md | 6 +- skills/debug-openshell-cluster/SKILL.md | 18 +- 53 files changed, 473 insertions(+), 916 deletions(-) diff --git a/.agents/skills/helm-dev-environment/SKILL.md b/.agents/skills/helm-dev-environment/SKILL.md index cb3023f476..932d0d905d 100644 --- a/.agents/skills/helm-dev-environment/SKILL.md +++ b/.agents/skills/helm-dev-environment/SKILL.md @@ -83,7 +83,10 @@ capability-free workload Pod and a directly managed capability-free supervisor Pod. One namespace-wide NetworkPolicy denies direct egress from every OpenShell workload Pod. The `pkiInitJob` hook (a pre-install Job that runs `openshell-gateway generate-certs`) -generates mTLS secrets on first install. The default Skaffold values export +generates gateway and CLI TLS secrets on first install. Supervisor Pods project +only `ca.crt` and authenticate gateway RPCs with sandbox bearer tokens. User +client certificates and private keys remain outside supervisor and workload Pods. +The default Skaffold values export gateway and Kubernetes-driver traces to the collector service installed by `helm:k3s:create`. Envoy Gateway is opt-in; see the Optional Add-ons section. diff --git a/crates/openshell-bootstrap/src/pki.rs b/crates/openshell-bootstrap/src/pki.rs index 5e5839a11a..e615e73fa3 100644 --- a/crates/openshell-bootstrap/src/pki.rs +++ b/crates/openshell-bootstrap/src/pki.rs @@ -93,7 +93,7 @@ pub fn generate_pki(extra_sans: &[String]) -> Result { .into_diagnostic() .wrap_err("failed to sign server certificate")?; - // --- Client cert (shared by CLI and sandbox pods) --- + // --- User client cert (CLI only; sandboxes use bearer identity) --- let client_key = KeyPair::generate() .into_diagnostic() .wrap_err("failed to generate client key")?; diff --git a/crates/openshell-core/src/config.rs b/crates/openshell-core/src/config.rs index c85fc4308a..820b4a9476 100644 --- a/crates/openshell-core/src/config.rs +++ b/crates/openshell-core/src/config.rs @@ -378,13 +378,13 @@ pub struct OidcConfig { pub scopes_claim: String, } -/// mTLS user authentication for local, single-user gateways. +/// mTLS user authentication for gateway users. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MtlsAuthConfig { /// When true, the gateway maps a verified TLS client certificate into a - /// user principal. Keep disabled for Kubernetes deployments because - /// Kubernetes sandbox pods and external users must not share user auth. + /// user principal. Sandbox and supervisor clients use bearer identity, so + /// this setting is independent of the selected compute driver. #[serde(default)] pub enabled: bool, } diff --git a/crates/openshell-core/src/container_paths.rs b/crates/openshell-core/src/container_paths.rs index 63511c13ff..e44f9fa8cb 100644 --- a/crates/openshell-core/src/container_paths.rs +++ b/crates/openshell-core/src/container_paths.rs @@ -44,8 +44,6 @@ pub const SUPERVISOR_CONTAINER_DIR: &str = "/opt/openshell/bin"; pub const SUPERVISOR_CONTAINER_BINARY: &str = "/opt/openshell/bin/openshell-sandbox"; pub const TLS_CLIENT_DIR: &str = "/etc/openshell/tls/client"; pub const TLS_CA_MOUNT_PATH: &str = "/etc/openshell/tls/client/ca.crt"; -pub const TLS_CERT_MOUNT_PATH: &str = "/etc/openshell/tls/client/tls.crt"; -pub const TLS_KEY_MOUNT_PATH: &str = "/etc/openshell/tls/client/tls.key"; pub const SANDBOX_TOKEN_MOUNT_PATH: &str = "/etc/openshell/auth/sandbox.jwt"; pub const UPSTREAM_PROXY_AUTH_MOUNT_PATH: &str = "/etc/openshell/auth/upstream-proxy"; pub const CONTAINER_POLICY_PATH: &str = "/etc/openshell/policy.yaml"; @@ -60,8 +58,6 @@ pub const SUPERVISOR_CA_CERT_PATH: &str = "/etc/openshell-tls/openshell-ca.pem"; pub const SUPERVISOR_CA_BUNDLE_PATH: &str = "/etc/openshell-tls/ca-bundle.pem"; pub const VM_GUEST_TLS_CA_PATH: &str = "/opt/openshell/tls/ca.crt"; -pub const VM_GUEST_TLS_CERT_PATH: &str = "/opt/openshell/tls/tls.crt"; -pub const VM_GUEST_TLS_KEY_PATH: &str = "/opt/openshell/tls/tls.key"; pub const VM_GUEST_SANDBOX_TOKEN_PATH: &str = "/opt/openshell/auth/sandbox.jwt"; pub const VM_GUEST_INIT_DROPIN_DIR: &str = "/opt/openshell/init.d"; pub const VM_GUEST_INIT_DROPIN_MANIFEST: &str = "/opt/openshell/init.d.manifest"; @@ -109,8 +105,6 @@ mod tests { SUPERVISOR_CONTAINER_BINARY, TLS_CLIENT_DIR, TLS_CA_MOUNT_PATH, - TLS_CERT_MOUNT_PATH, - TLS_KEY_MOUNT_PATH, SANDBOX_TOKEN_MOUNT_PATH, UPSTREAM_PROXY_AUTH_MOUNT_PATH, CONTAINER_POLICY_PATH, @@ -123,8 +117,6 @@ mod tests { SUPERVISOR_CA_CERT_PATH, SUPERVISOR_CA_BUNDLE_PATH, VM_GUEST_TLS_CA_PATH, - VM_GUEST_TLS_CERT_PATH, - VM_GUEST_TLS_KEY_PATH, VM_GUEST_SANDBOX_TOKEN_PATH, VM_GUEST_UPSTREAM_PROXY_AUTH_PATH, VM_GUEST_PROXY_CA_PATH, diff --git a/crates/openshell-core/src/driver_utils.rs b/crates/openshell-core/src/driver_utils.rs index a5a86fe908..71fb790b11 100644 --- a/crates/openshell-core/src/driver_utils.rs +++ b/crates/openshell-core/src/driver_utils.rs @@ -104,22 +104,15 @@ pub const SUPERVISOR_CONTAINER_BINARY: &str = "/opt/openshell/bin/openshell-sand // --------------------------------------------------------------------------- // In-container mount paths for guest TLS materials and the sandbox token. // -// All container-based drivers (Docker, Podman, Kubernetes) mount the gateway's -// mTLS client credentials at these fixed paths inside every sandbox container. -// The supervisor reads these paths on startup to establish its gRPC-over-mTLS -// connection back to the gateway. The paths must remain stable across driver -// versions since the supervisor binary is built and packaged separately. +// Container-based drivers mount the gateway CA at this fixed path inside every +// supervisor container. The supervisor reads it on startup to authenticate the +// gateway TLS endpoint. Sandbox identity is provided separately by a bearer +// token. // --------------------------------------------------------------------------- -/// Container-side mount path for the guest mTLS CA certificate. +/// Container-side mount path for the gateway CA certificate. pub const TLS_CA_MOUNT_PATH: &str = "/etc/openshell/tls/client/ca.crt"; -/// Container-side mount path for the guest mTLS client certificate. -pub const TLS_CERT_MOUNT_PATH: &str = "/etc/openshell/tls/client/tls.crt"; - -/// Container-side mount path for the guest mTLS client private key. -pub const TLS_KEY_MOUNT_PATH: &str = "/etc/openshell/tls/client/tls.key"; - /// Container-side mount path for the per-sandbox JWT token. pub const SANDBOX_TOKEN_MOUNT_PATH: &str = "/etc/openshell/auth/sandbox.jwt"; diff --git a/crates/openshell-core/src/grpc_client.rs b/crates/openshell-core/src/grpc_client.rs index 9808bd16e0..f95314fece 100644 --- a/crates/openshell-core/src/grpc_client.rs +++ b/crates/openshell-core/src/grpc_client.rs @@ -40,7 +40,7 @@ use openshell_extension_core::{BearerTokenSlot, ExtensionCredentialStore}; use tonic::Status; use tonic::metadata::AsciiMetadataValue; use tonic::service::interceptor::InterceptedService; -use tonic::transport::{Certificate, Channel, ClientTlsConfig, Endpoint, Identity}; +use tonic::transport::{Certificate, Channel, ClientTlsConfig, Endpoint}; use tracing::{debug, info, warn}; /// Preserve the gRPC status as a source so callers can classify retryable errors. @@ -201,10 +201,9 @@ impl tonic::service::Interceptor for AuthInterceptor { /// Build the plain (un-intercepted) gRPC channel. /// -/// When the endpoint uses `https://`, mTLS is configured using these env vars: +/// When the endpoint uses `https://`, server-authenticated TLS is configured +/// using this env var: /// - `OPENSHELL_TLS_CA` -- path to the CA certificate -/// - `OPENSHELL_TLS_CERT` -- path to the client certificate -/// - `OPENSHELL_TLS_KEY` -- path to the client private key /// /// When the endpoint uses `http://`, a plaintext connection is used (for /// deployments where TLS is disabled, e.g. behind a Cloudflare Tunnel). @@ -223,7 +222,7 @@ async fn build_plain_channel(endpoint: &str) -> Result { let tls_enabled = endpoint.starts_with("https://"); - // TODO: TLS certs are loaded once here and never re-read. The gateway + // TODO: The TLS CA is loaded once here and never re-read. The gateway // server side supports hot-reload (ArcSwap + notify in tls.rs). The // supervisor should do the same so that cert-manager rotations take // effect without restarting the sandbox. @@ -231,26 +230,12 @@ async fn build_plain_channel(endpoint: &str) -> Result { let ca_path = std::env::var(sandbox_env::TLS_CA) .into_diagnostic() .wrap_err("OPENSHELL_TLS_CA is required")?; - let cert_path = std::env::var(sandbox_env::TLS_CERT) - .into_diagnostic() - .wrap_err("OPENSHELL_TLS_CERT is required")?; - let key_path = std::env::var(sandbox_env::TLS_KEY) - .into_diagnostic() - .wrap_err("OPENSHELL_TLS_KEY is required")?; - let ca_pem = std::fs::read(&ca_path) .into_diagnostic() .wrap_err_with(|| format!("failed to read CA cert from {ca_path}"))?; - let cert_pem = std::fs::read(&cert_path) - .into_diagnostic() - .wrap_err_with(|| format!("failed to read client cert from {cert_path}"))?; - let key_pem = std::fs::read(&key_path) - .into_diagnostic() - .wrap_err_with(|| format!("failed to read client key from {key_path}"))?; // Trust only the configured CA — this is the chart's internal CA - // that signs both the gateway's internal server certificate and - // this client's identity certificate. The gateway uses SNI-based + // that signs the gateway's internal server certificate. The gateway uses SNI-based // certificate selection to present this internal cert to supervisor // connections, so no public root trust is needed here. // @@ -259,9 +244,7 @@ async fn build_plain_channel(endpoint: &str) -> Result { // (Docker/Podman drivers), and broadening the trust store would let // an attacker who controls the image + DNS present a publicly valid // certificate and intercept the supervisor→gateway TLS connection. - let mut tls_config = ClientTlsConfig::new() - .ca_certificate(Certificate::from_pem(ca_pem)) - .identity(Identity::from_pem(cert_pem, key_pem)); + let mut tls_config = ClientTlsConfig::new().ca_certificate(Certificate::from_pem(ca_pem)); if let Ok(server_name) = std::env::var(sandbox_env::GATEWAY_TLS_SERVER_NAME) && !server_name.is_empty() { diff --git a/crates/openshell-core/src/sandbox_env.rs b/crates/openshell-core/src/sandbox_env.rs index 24640c7908..9ac843cecd 100644 --- a/crates/openshell-core/src/sandbox_env.rs +++ b/crates/openshell-core/src/sandbox_env.rs @@ -185,7 +185,7 @@ pub const PROXY_CA_KEY: &str = "OPENSHELL_PROXY_CA_KEY"; /// Whether the control-owned SSH Unix socket is shared across trusted UIDs. pub const SSH_SOCKET_SHARED: &str = "OPENSHELL_SSH_SOCKET_SHARED"; -/// Path to the CA certificate for mTLS communication with the gateway. +/// Path to the CA certificate used to authenticate the gateway TLS endpoint. pub const TLS_CA: &str = "OPENSHELL_TLS_CA"; /// Path to the client certificate for mTLS communication with the gateway. diff --git a/crates/openshell-driver-docker/README.md b/crates/openshell-driver-docker/README.md index ee329f8dea..c4a551caa0 100644 --- a/crates/openshell-driver-docker/README.md +++ b/crates/openshell-driver-docker/README.md @@ -177,6 +177,10 @@ The driver publishes host loopback as the backend address for mediated path, so policies can reach host services without a Docker bridge, container DNS alias, or another gateway listener. +For HTTPS endpoints, the supervisor receives only the gateway CA and +uses its sandbox-scoped bearer token to authenticate RPCs. User client +certificates and private keys are not delivered to either container. + Docker Engine on Linux supports host networking directly. Docker Desktop requires host networking to be enabled in Settings and does not support it when Enhanced Container Isolation is enabled. @@ -189,7 +193,7 @@ The supervisor owns these security-critical variables: - `OPENSHELL_SANDBOX_TOKEN_FILE` - `OPENSHELL_SSH_SOCKET_PATH` - `OPENSHELL_MAIN_PROCESS_SPEC` -- TLS path variables when HTTPS is enabled +- `OPENSHELL_TLS_CA` when HTTPS is enabled Template and sandbox environment is encoded in the protected bootstrap and exposed only to workload children. Workload input cannot override diff --git a/crates/openshell-driver-docker/src/lib.rs b/crates/openshell-driver-docker/src/lib.rs index d4a1f2f71d..4c9d2ac2bc 100644 --- a/crates/openshell-driver-docker/src/lib.rs +++ b/crates/openshell-driver-docker/src/lib.rs @@ -189,13 +189,15 @@ pub struct DockerComputeConfig { /// Image containing the trusted `openshell-supervisor` binary. pub supervisor_image: Option, - /// Host-side CA certificate for Docker sandbox mTLS. + /// Host-side CA certificate for sandbox-to-gateway TLS. pub guest_tls_ca: Option, - /// Host-side client certificate for Docker sandbox mTLS. + /// Deprecated. Sandboxes authenticate with bearer tokens and must not + /// receive a user client certificate. pub guest_tls_cert: Option, - /// Host-side private key for Docker sandbox mTLS. + /// Deprecated. Sandboxes authenticate with bearer tokens and must not + /// receive a user client private key. pub guest_tls_key: Option, /// Unix socket path used for interactive sandbox access. @@ -293,8 +295,6 @@ impl Default for DockerComputeConfig { #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct DockerGuestTlsPaths { pub(crate) ca: PathBuf, - pub(crate) cert: PathBuf, - pub(crate) key: PathBuf, } #[derive(Debug, Clone)] @@ -4676,11 +4676,7 @@ async fn docker_supervisor_bundle_archive( SUPERVISOR_UID, SUPERVISOR_GID, )?; - for (name, path) in [ - ("ca.pem", &tls.ca), - ("cert.pem", &tls.cert), - ("key.pem", &tls.key), - ] { + for (name, path) in [("ca.pem", &tls.ca)] { let contents = tokio::fs::read(path).await.map_err(|error| { Status::internal(format!( "read Docker supervisor TLS file {}: {error}", @@ -5097,20 +5093,10 @@ async fn spawn_docker_control_process( ), ]; if config.guest_tls.is_some() { - environment.extend([ - format!( - "{}={SUPERVISOR_STATE_MOUNT_PATH}/tls/ca.pem", - openshell_core::sandbox_env::TLS_CA - ), - format!( - "{}={SUPERVISOR_STATE_MOUNT_PATH}/tls/cert.pem", - openshell_core::sandbox_env::TLS_CERT - ), - format!( - "{}={SUPERVISOR_STATE_MOUNT_PATH}/tls/key.pem", - openshell_core::sandbox_env::TLS_KEY - ), - ]); + environment.push(format!( + "{}={SUPERVISOR_STATE_MOUNT_PATH}/tls/ca.pem", + openshell_core::sandbox_env::TLS_CA + )); } if let Some(socket) = config.provider_spiffe_workload_api_socket.as_ref() { let projected = openshell_core::driver_utils::projected_provider_spiffe_socket_path(socket) @@ -6505,8 +6491,6 @@ fn canonicalize_existing_file(path: &Path, description: &str) -> CoreResult bool { docker_config.guest_tls_ca.is_some() - && docker_config.guest_tls_cert.is_some() - && docker_config.guest_tls_key.is_some() } fn default_docker_supervisor_grpc_endpoint(gateway_port: u16, tls: bool) -> String { @@ -6521,24 +6505,25 @@ pub(crate) fn docker_guest_tls_paths( || docker_config.guest_tls_cert.is_some() || docker_config.guest_tls_key.is_some(); + if docker_config.guest_tls_cert.is_some() || docker_config.guest_tls_key.is_some() { + return Err(Error::config( + "guest_tls_cert and guest_tls_key are no longer supported; sandboxes authenticate to the gateway with bearer tokens", + )); + } + if !docker_config.grpc_endpoint.starts_with("https://") { if tls_flags_provided { return Err(Error::config(format!( - "guest_tls_ca/guest_tls_cert/guest_tls_key were provided but grpc_endpoint is '{}'; TLS materials require an https:// endpoint", + "guest_tls_ca was provided but grpc_endpoint is '{}'; TLS materials require an https:// endpoint", docker_config.grpc_endpoint, ))); } return Ok(None); } - let provided = [ - docker_config.guest_tls_ca.as_ref(), - docker_config.guest_tls_cert.as_ref(), - docker_config.guest_tls_key.as_ref(), - ]; - if provided.iter().all(Option::is_none) { + if docker_config.guest_tls_ca.is_none() { return Err(Error::config( - "docker compute driver requires guest_tls_ca, guest_tls_cert, and guest_tls_key when grpc_endpoint uses https://", + "docker compute driver requires guest_tls_ca when grpc_endpoint uses https://", )); } @@ -6547,21 +6532,8 @@ pub(crate) fn docker_guest_tls_paths( "guest_tls_ca is required when Docker sandbox TLS materials are configured", )); }; - let Some(cert) = docker_config.guest_tls_cert.clone() else { - return Err(Error::config( - "guest_tls_cert is required when Docker sandbox TLS materials are configured", - )); - }; - let Some(key) = docker_config.guest_tls_key.clone() else { - return Err(Error::config( - "guest_tls_key is required when Docker sandbox TLS materials are configured", - )); - }; - Ok(Some(DockerGuestTlsPaths { ca: canonicalize_existing_file(&ca, "docker TLS CA certificate")?, - cert: canonicalize_existing_file(&cert, "docker TLS client certificate")?, - key: canonicalize_existing_file(&key, "docker TLS client private key")?, })) } diff --git a/crates/openshell-driver-docker/src/tests.rs b/crates/openshell-driver-docker/src/tests.rs index bc747312c3..8aa05302e0 100644 --- a/crates/openshell-driver-docker/src/tests.rs +++ b/crates/openshell-driver-docker/src/tests.rs @@ -188,8 +188,6 @@ fn runtime_config() -> DockerDriverRuntimeConfig { ssh_socket_path: openshell_core::container_paths::SSH_SOCKET_PATH.to_string(), guest_tls: Some(DockerGuestTlsPaths { ca: PathBuf::from("/tmp/ca.crt"), - cert: PathBuf::from("/tmp/tls.crt"), - key: PathBuf::from("/tmp/tls.key"), }), gpu: DockerGpuRuntimeCapabilities { cdi_supported: false, @@ -3237,18 +3235,19 @@ fn workload_mounts_only_the_shared_channel_volume() { } #[test] -fn docker_guest_tls_paths_require_all_files_for_https() { +fn docker_guest_tls_paths_accept_ca_only_for_https() { let tempdir = TempDir::new().unwrap(); let ca = tempdir.path().join("ca.crt"); fs::write(&ca, b"ca").unwrap(); - let err = docker_guest_tls_paths(&DockerComputeConfig { + let paths = docker_guest_tls_paths(&DockerComputeConfig { grpc_endpoint: "https://localhost:8443".to_string(), - guest_tls_ca: Some(ca), + guest_tls_ca: Some(ca.clone()), ..Default::default() }) - .unwrap_err(); - assert!(err.to_string().contains("guest_tls_cert")); + .unwrap() + .expect("CA-only TLS paths"); + assert_eq!(paths.ca, ca.canonicalize().unwrap()); } #[test] diff --git a/crates/openshell-driver-kubernetes/README.md b/crates/openshell-driver-kubernetes/README.md index 215a7b21e1..6cb277fbbb 100644 --- a/crates/openshell-driver-kubernetes/README.md +++ b/crates/openshell-driver-kubernetes/README.md @@ -157,7 +157,10 @@ UID. Restart requires exactly one matching Sandbox resource and preserves its namespace and UID while rotating the supervisor Pod UID. The gateway requires the authenticated identity to match the durable binding before returning the generation-bound session JWT used by the supervisor. The sandbox Pod receives -neither token. +neither token. For HTTPS gateway connections, the supervisor reads only the +CA from the configured TLS Secret. Shared mode projects `ca.crt` directly; +managed and operator modes stage only the CA into the supervisor bootstrap +Secret. User client certificates and private keys are not mounted into either Pod. The gateway uses the supervisor relay for connect, exec, logs, and file sync. Sandbox Pods do not need direct external ingress for SSH. diff --git a/crates/openshell-driver-kubernetes/src/config.rs b/crates/openshell-driver-kubernetes/src/config.rs index d454014708..992c9741ab 100644 --- a/crates/openshell-driver-kubernetes/src/config.rs +++ b/crates/openshell-driver-kubernetes/src/config.rs @@ -624,7 +624,7 @@ impl KubernetesComputeConfig { !matches!(self.workspace_mode, WorkspaceMode::Shared) } - /// Where supervisor Pods read the gateway client TLS material. Outside + /// Where supervisor Pods read the gateway CA. Outside /// shared mode it is staged into each generation's bootstrap Secret. #[must_use] pub fn supervisor_client_tls(&self) -> crate::sandbox_runtime::SupervisorClientTls<'_> { diff --git a/crates/openshell-driver-kubernetes/src/driver.rs b/crates/openshell-driver-kubernetes/src/driver.rs index 425f6daaca..64ffb0ca97 100644 --- a/crates/openshell-driver-kubernetes/src/driver.rs +++ b/crates/openshell-driver-kubernetes/src/driver.rs @@ -1251,8 +1251,8 @@ impl KubernetesComputeDriver { Ok(()) } - /// Read the gateway client TLS material staged into supervisor bootstrap - /// Secrets outside the sandbox namespace. + /// Read only the gateway CA staged into supervisor bootstrap Secrets + /// outside the sandbox namespace. async fn read_client_tls_material( &self, ) -> Result, KubernetesDriverError> { @@ -1272,8 +1272,6 @@ impl KubernetesComputeDriver { }; Ok(Some(ClientTlsMaterial { ca_certificate: take("ca.crt")?, - certificate: take("tls.crt")?, - private_key: take("tls.key")?, })) } @@ -11244,7 +11242,7 @@ mod tests { "kind": "Secret", "metadata": {"name": "openshell-client-tls", "namespace": "openshell"}, "type": "kubernetes.io/tls", - "data": {"ca.crt": "Y2E=", "tls.crt": "Y2VydA==", "tls.key": "a2V5"} + "data": {"ca.crt": "Y2E="} }), ), )], @@ -11255,8 +11253,6 @@ mod tests { .expect("read client TLS") .expect("client TLS is staged in managed mode"); assert_eq!(material.ca_certificate, b"ca"); - assert_eq!(material.certificate, b"cert"); - assert_eq!(material.private_key, b"key"); assert!(steps.lock().unwrap().is_empty()); let (shared, _, _) = scripted_driver( diff --git a/crates/openshell-driver-kubernetes/src/sandbox_runtime.rs b/crates/openshell-driver-kubernetes/src/sandbox_runtime.rs index abee5eaf03..642809aff5 100644 --- a/crates/openshell-driver-kubernetes/src/sandbox_runtime.rs +++ b/crates/openshell-driver-kubernetes/src/sandbox_runtime.rs @@ -39,8 +39,6 @@ pub const PROXY_CA_PRIVATE_KEY: &str = "proxy-ca.key"; /// which is the sandbox's own generated TLS-interception CA. pub const UPSTREAM_PROXY_CA_BUNDLE_KEY: &str = "upstream-proxy-ca.pem"; pub const CLIENT_TLS_CA_KEY: &str = "client-ca.crt"; -pub const CLIENT_TLS_CERTIFICATE_KEY: &str = "client-tls.crt"; -pub const CLIENT_TLS_PRIVATE_KEY: &str = "client-tls.key"; pub const SANDBOX_BOOTSTRAP_INPUT_PATH: &str = "/.openshell/bootstrap-input"; pub const BOUNDARY_CONFIG_PATH: &str = "/.openshell/state/bootstrap/boundary.json"; pub const BOUNDARY_CERTIFICATE_PATH: &str = "/.openshell/state/bootstrap/tls.crt"; @@ -56,8 +54,6 @@ pub const PROXY_CA_PRIVATE_KEY_PATH: &str = "/.openshell/supervisor/proxy-ca.key /// without a dedicated volume or mount. pub const UPSTREAM_PROXY_CA_BUNDLE_PATH: &str = "/.openshell/supervisor/upstream-proxy-ca.pem"; pub const CLIENT_TLS_CA_PATH: &str = "/.openshell/supervisor/client-ca.crt"; -pub const CLIENT_TLS_CERTIFICATE_PATH: &str = "/.openshell/supervisor/client-tls.crt"; -pub const CLIENT_TLS_PRIVATE_KEY_PATH: &str = "/.openshell/supervisor/client-tls.key"; pub const CONTROL_HEALTH_SOCKET_PATH: &str = "/run/openshell/health.sock"; /// Kubelet `tcpSocket` readiness port. An exec probe would start a supervisor /// process in every sandbox on every period. @@ -66,15 +62,13 @@ pub const NAMESPACE_WORKLOAD_POLICY_NAME: &str = "openshell-sandbox-workloads"; pub const NAMESPACE_SUPERVISOR_EGRESS_POLICY_NAME: &str = "openshell-sandbox-supervisors"; pub const SUPERVISOR_TERMINATION_GRACE_PERIOD_SECONDS: i64 = 30; -/// Gateway client TLS material staged into the supervisor bootstrap Secret. +/// Gateway CA material staged into the supervisor bootstrap Secret. #[derive(Clone, Debug, PartialEq, Eq)] pub struct ClientTlsMaterial { pub ca_certificate: Vec, - pub certificate: Vec, - pub private_key: Vec, } -/// Where the supervisor reads its gateway client TLS material. +/// Where the supervisor reads the gateway CA. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum SupervisorClientTls<'a> { Disabled, @@ -304,29 +298,28 @@ pub fn supervisor_pod( match client_tls { SupervisorClientTls::Disabled => {} SupervisorClientTls::Secret(secret_name) => { - environment.extend([ - env_var("OPENSHELL_TLS_CA", "/var/run/secrets/openshell-tls/ca.crt"), - env_var( - "OPENSHELL_TLS_CERT", - "/var/run/secrets/openshell-tls/tls.crt", - ), - env_var( - "OPENSHELL_TLS_KEY", - "/var/run/secrets/openshell-tls/tls.key", - ), - ]); + environment.push(env_var( + "OPENSHELL_TLS_CA", + "/var/run/secrets/openshell-tls/ca.crt", + )); volume_mounts.push(volume_mount( "client-tls", "/var/run/secrets/openshell-tls", true, )); - volumes.push(secret_volume("client-tls", secret_name, None)); + volumes.push(secret_volume( + "client-tls", + secret_name, + Some(KeyToPath { + key: "ca.crt".to_string(), + path: "ca.crt".to_string(), + ..Default::default() + }), + )); + } + SupervisorClientTls::Bootstrap => { + environment.push(env_var("OPENSHELL_TLS_CA", CLIENT_TLS_CA_PATH)); } - SupervisorClientTls::Bootstrap => environment.extend([ - env_var("OPENSHELL_TLS_CA", CLIENT_TLS_CA_PATH), - env_var("OPENSHELL_TLS_CERT", CLIENT_TLS_CERTIFICATE_PATH), - env_var("OPENSHELL_TLS_KEY", CLIENT_TLS_PRIVATE_KEY_PATH), - ]), } let mut command = vec![ "/openshell-supervisor".to_string(), @@ -560,20 +553,10 @@ pub fn supervisor_bootstrap_secret( data.insert(UPSTREAM_PROXY_CA_BUNDLE_KEY.to_string(), ByteString(bundle)); } if let Some(tls) = client_tls { - data.extend([ - ( - CLIENT_TLS_CA_KEY.to_string(), - ByteString(tls.ca_certificate), - ), - ( - CLIENT_TLS_CERTIFICATE_KEY.to_string(), - ByteString(tls.certificate), - ), - ( - CLIENT_TLS_PRIVATE_KEY.to_string(), - ByteString(tls.private_key), - ), - ]); + data.insert( + CLIENT_TLS_CA_KEY.to_string(), + ByteString(tls.ca_certificate), + ); } Secret { metadata: ObjectMeta { @@ -793,15 +776,13 @@ mod tests { None, Some(ClientTlsMaterial { ca_certificate: b"ca".to_vec(), - certificate: b"cert".to_vec(), - private_key: b"key".to_vec(), }), owner(), ); let data = secret.data.expect("Secret data"); assert_eq!(data[CLIENT_TLS_CA_KEY].0, b"ca"); - assert_eq!(data[CLIENT_TLS_CERTIFICATE_KEY].0, b"cert"); - assert_eq!(data[CLIENT_TLS_PRIVATE_KEY].0, b"key"); + assert!(!data.contains_key("client-tls.crt")); + assert!(!data.contains_key("client-tls.key")); } fn supervisor_pod_with_client_tls(client_tls: SupervisorClientTls<'_>) -> Pod { @@ -865,8 +846,8 @@ mod tests { SupervisorClientTls::Bootstrap, )); assert_eq!(env["OPENSHELL_TLS_CA"], CLIENT_TLS_CA_PATH); - assert_eq!(env["OPENSHELL_TLS_CERT"], CLIENT_TLS_CERTIFICATE_PATH); - assert_eq!(env["OPENSHELL_TLS_KEY"], CLIENT_TLS_PRIVATE_KEY_PATH); + assert!(!env.contains_key("OPENSHELL_TLS_CERT")); + assert!(!env.contains_key("OPENSHELL_TLS_KEY")); assert!(CLIENT_TLS_CA_PATH.starts_with("/.openshell/supervisor/")); assert!(!volumes.contains(&"client-tls".to_string())); } @@ -879,6 +860,21 @@ mod tests { env["OPENSHELL_TLS_CA"], "/var/run/secrets/openshell-tls/ca.crt" ); + assert!(!env.contains_key("OPENSHELL_TLS_CERT")); + assert!(!env.contains_key("OPENSHELL_TLS_KEY")); + let tls_volume = pod + .spec + .as_ref() + .unwrap() + .volumes + .as_ref() + .unwrap() + .iter() + .find(|volume| volume.name == "client-tls") + .unwrap(); + let items = tls_volume.secret.as_ref().unwrap().items.as_ref().unwrap(); + assert_eq!(items.len(), 1); + assert_eq!(items[0].key, "ca.crt"); assert!(volumes.contains(&"client-tls".to_string())); } @@ -936,6 +932,31 @@ mod tests { None ); let container = &pod_spec.containers[0]; + let environment = container.env.as_ref().expect("supervisor environment"); + assert!( + environment + .iter() + .any(|entry| entry.name == "OPENSHELL_TLS_CA") + ); + assert!(!environment.iter().any(|entry| matches!( + entry.name.as_str(), + "OPENSHELL_TLS_CERT" | "OPENSHELL_TLS_KEY" + ))); + let gateway_tls = pod_spec + .volumes + .as_ref() + .expect("supervisor volumes") + .iter() + .find(|volume| volume.name == "client-tls") + .and_then(|volume| volume.secret.as_ref()) + .expect("gateway CA secret"); + let items = gateway_tls + .items + .as_ref() + .expect("CA-only secret projection"); + assert_eq!(items.len(), 1); + assert_eq!(items[0].key, "ca.crt"); + assert_eq!(items[0].path, "ca.crt"); assert_eq!(container.image_pull_policy.as_deref(), Some("IfNotPresent")); assert_eq!(pod_spec.automount_service_account_token, Some(false)); assert_eq!(pod_spec.restart_policy.as_deref(), Some("Never")); diff --git a/crates/openshell-driver-podman/README.md b/crates/openshell-driver-podman/README.md index 133326a0ec..abf2147513 100644 --- a/crates/openshell-driver-podman/README.md +++ b/crates/openshell-driver-podman/README.md @@ -69,8 +69,9 @@ Landlock denies agent access to the top-level `/.openshell` control hierarchy. The driver verifies Podman's reported `network=none` fence before launch and restart. Host networking applies to the supervisor, not the agent. -Gateway sessions use the existing sandbox JWT and optional configured mTLS -bundle. The sandbox/supervisor channel always uses its separate, per-sandbox +Gateway sessions use the sandbox JWT and optional server-authenticated TLS. +Only the gateway CA is delivered to the supervisor; user client certificates +and private keys are not mounted into either container. The sandbox/supervisor channel always uses its separate, per-sandbox mutual TLS material. These are distinct authentication relationships. ## Identity and trusted binaries diff --git a/crates/openshell-driver-podman/src/config.rs b/crates/openshell-driver-podman/src/config.rs index e5a7802d2e..4559cdd65e 100644 --- a/crates/openshell-driver-podman/src/config.rs +++ b/crates/openshell-driver-podman/src/config.rs @@ -69,16 +69,14 @@ pub struct PodmanComputeConfig { pub sandbox_runtime_image: String, /// OCI image containing the dynamically linked `openshell-supervisor` binary. pub supervisor_image: String, - /// Host path to the CA certificate for sandbox mTLS. + /// Host path to the CA certificate for sandbox-to-gateway TLS. /// - /// When all three TLS paths (`guest_tls_ca`, `guest_tls_cert`, - /// `guest_tls_key`) are set, the driver bind-mounts them into sandbox - /// containers and switches the auto-detected endpoint from `http://` - /// to `https://`. + /// When set, the driver mounts the CA into supervisor containers and + /// switches the auto-detected endpoint from `http://` to `https://`. pub guest_tls_ca: Option, - /// Host path to the client certificate for sandbox mTLS. + /// Deprecated. Sandboxes authenticate with bearer tokens. pub guest_tls_cert: Option, - /// Host path to the client private key for sandbox mTLS. + /// Deprecated. Sandboxes authenticate with bearer tokens. pub guest_tls_key: Option, /// Container cgroup PID limit for Podman-managed sandboxes. /// @@ -253,43 +251,33 @@ impl PodmanComputeConfig { self.validate_userns_mappings() } - /// Returns `true` when all three TLS paths are configured. + /// Returns `true` when the gateway CA is configured. #[must_use] pub fn tls_enabled(&self) -> bool { - self.guest_tls_ca.is_some() && self.guest_tls_cert.is_some() && self.guest_tls_key.is_some() + self.guest_tls_ca.is_some() } /// Validate TLS configuration consistency. /// - /// Returns `Ok(())` when either all three TLS paths are set (full mTLS) - /// or none are set (plaintext). Returns an error naming the missing - /// fields when only a subset is provided — this prevents silent - /// fallback to plaintext when an operator partially configures mTLS. + /// Client certificates are rejected because sandbox identity is carried + /// by bearer tokens rather than the gateway user's mTLS identity. pub fn validate_tls_config(&self) -> Result<(), crate::client::PodmanApiError> { - let has_ca = self.guest_tls_ca.is_some(); let has_cert = self.guest_tls_cert.is_some(); let has_key = self.guest_tls_key.is_some(); - // All set or none set — both are valid. - if (has_ca && has_cert && has_key) || (!has_ca && !has_cert && !has_key) { + if !has_cert && !has_key { return Ok(()); } - - let mut missing = Vec::new(); - if !has_ca { - missing.push("--podman-tls-ca / OPENSHELL_PODMAN_TLS_CA"); - } - if !has_cert { - missing.push("--podman-tls-cert / OPENSHELL_PODMAN_TLS_CERT"); - } - if !has_key { - missing.push("--podman-tls-key / OPENSHELL_PODMAN_TLS_KEY"); - } - Err(crate::client::PodmanApiError::InvalidInput(format!( - "Partial TLS configuration: all three TLS paths must be provided together. \ - Missing: {}", - missing.join(", ") + "Sandbox client certificates are no longer supported; remove {}", + [ + has_cert.then_some("--podman-tls-cert / OPENSHELL_PODMAN_TLS_CERT"), + has_key.then_some("--podman-tls-key / OPENSHELL_PODMAN_TLS_KEY"), + ] + .into_iter() + .flatten() + .collect::>() + .join(" and ") ))) } @@ -1030,107 +1018,39 @@ mod tests { } #[test] - fn validate_tls_config_all_set_is_ok() { + fn validate_tls_config_ca_only_is_ok() { let cfg = PodmanComputeConfig { guest_tls_ca: Some(PathBuf::from("/tls/ca.crt")), - guest_tls_cert: Some(PathBuf::from("/tls/tls.crt")), - guest_tls_key: Some(PathBuf::from("/tls/tls.key")), ..PodmanComputeConfig::default() }; assert!(cfg.validate_tls_config().is_ok()); + assert!(cfg.tls_enabled()); } #[test] - fn validate_tls_config_only_ca_is_error() { - let cfg = PodmanComputeConfig { - guest_tls_ca: Some(PathBuf::from("/tls/ca.crt")), - ..PodmanComputeConfig::default() - }; - let err = cfg - .validate_tls_config() - .expect_err("only CA should be rejected"); - let msg = err.to_string(); - assert!(msg.contains("OPENSHELL_PODMAN_TLS_CERT"), "{msg}"); - assert!(msg.contains("OPENSHELL_PODMAN_TLS_KEY"), "{msg}"); - assert!(!msg.contains("OPENSHELL_PODMAN_TLS_CA"), "{msg}"); - } - - #[test] - fn validate_tls_config_only_cert_is_error() { - let cfg = PodmanComputeConfig { - guest_tls_cert: Some(PathBuf::from("/tls/tls.crt")), - ..PodmanComputeConfig::default() - }; - let err = cfg - .validate_tls_config() - .expect_err("only cert should be rejected"); - let msg = err.to_string(); - assert!(msg.contains("OPENSHELL_PODMAN_TLS_CA"), "{msg}"); - assert!(msg.contains("OPENSHELL_PODMAN_TLS_KEY"), "{msg}"); - assert!(!msg.contains("OPENSHELL_PODMAN_TLS_CERT"), "{msg}"); - } - - #[test] - fn validate_tls_config_only_key_is_error() { - let cfg = PodmanComputeConfig { - guest_tls_key: Some(PathBuf::from("/tls/tls.key")), - ..PodmanComputeConfig::default() - }; - let err = cfg - .validate_tls_config() - .expect_err("only key should be rejected"); - let msg = err.to_string(); - assert!(msg.contains("OPENSHELL_PODMAN_TLS_CA"), "{msg}"); - assert!(msg.contains("OPENSHELL_PODMAN_TLS_CERT"), "{msg}"); - assert!(!msg.contains("OPENSHELL_PODMAN_TLS_KEY"), "{msg}"); - } - - #[test] - fn validate_tls_config_ca_and_cert_missing_key_is_error() { + fn validate_tls_config_rejects_client_certificate() { let cfg = PodmanComputeConfig { - guest_tls_ca: Some(PathBuf::from("/tls/ca.crt")), guest_tls_cert: Some(PathBuf::from("/tls/tls.crt")), ..PodmanComputeConfig::default() }; let err = cfg .validate_tls_config() - .expect_err("missing key should be rejected"); - let msg = err.to_string(); - assert!(msg.contains("OPENSHELL_PODMAN_TLS_KEY"), "{msg}"); - assert!(!msg.contains("OPENSHELL_PODMAN_TLS_CA"), "{msg}"); - assert!(!msg.contains("OPENSHELL_PODMAN_TLS_CERT"), "{msg}"); - } - - #[test] - fn validate_tls_config_ca_and_key_missing_cert_is_error() { - let cfg = PodmanComputeConfig { - guest_tls_ca: Some(PathBuf::from("/tls/ca.crt")), - guest_tls_key: Some(PathBuf::from("/tls/tls.key")), - ..PodmanComputeConfig::default() - }; - let err = cfg - .validate_tls_config() - .expect_err("missing cert should be rejected"); + .expect_err("sandbox client certificate should be rejected"); let msg = err.to_string(); assert!(msg.contains("OPENSHELL_PODMAN_TLS_CERT"), "{msg}"); - assert!(!msg.contains("OPENSHELL_PODMAN_TLS_CA"), "{msg}"); - assert!(!msg.contains("OPENSHELL_PODMAN_TLS_KEY"), "{msg}"); } #[test] - fn validate_tls_config_cert_and_key_missing_ca_is_error() { + fn validate_tls_config_rejects_client_private_key() { let cfg = PodmanComputeConfig { - guest_tls_cert: Some(PathBuf::from("/tls/tls.crt")), guest_tls_key: Some(PathBuf::from("/tls/tls.key")), ..PodmanComputeConfig::default() }; let err = cfg .validate_tls_config() - .expect_err("missing CA should be rejected"); + .expect_err("sandbox client private key should be rejected"); let msg = err.to_string(); - assert!(msg.contains("OPENSHELL_PODMAN_TLS_CA"), "{msg}"); - assert!(!msg.contains("OPENSHELL_PODMAN_TLS_CERT"), "{msg}"); - assert!(!msg.contains("OPENSHELL_PODMAN_TLS_KEY"), "{msg}"); + assert!(msg.contains("OPENSHELL_PODMAN_TLS_KEY"), "{msg}"); } #[test] diff --git a/crates/openshell-driver-podman/src/container.rs b/crates/openshell-driver-podman/src/container.rs index 3092eb73b3..51245a244a 100644 --- a/crates/openshell-driver-podman/src/container.rs +++ b/crates/openshell-driver-podman/src/container.rs @@ -55,13 +55,9 @@ const TOKEN_SECRET_PREFIX: &str = "openshell-token-"; const PROXY_AUTH_SECRET_PREFIX: &str = "openshell-proxy-auth-"; const RESOLVER_SECRET_PREFIX: &str = "openshell-resolver-"; const TLS_CA_SECRET_PREFIX: &str = "openshell-tls-ca-"; -const TLS_CERT_SECRET_PREFIX: &str = "openshell-tls-cert-"; -const TLS_KEY_SECRET_PREFIX: &str = "openshell-tls-key-"; /// Container-side mount paths for client TLS materials and the sandbox token. const TLS_CA_MOUNT_PATH: &str = openshell_core::driver_utils::TLS_CA_MOUNT_PATH; -const TLS_CERT_MOUNT_PATH: &str = openshell_core::driver_utils::TLS_CERT_MOUNT_PATH; -const TLS_KEY_MOUNT_PATH: &str = openshell_core::driver_utils::TLS_KEY_MOUNT_PATH; const SANDBOX_TOKEN_MOUNT_PATH: &str = openshell_core::driver_utils::SANDBOX_TOKEN_MOUNT_PATH; const UPSTREAM_PROXY_AUTH_MOUNT_PATH: &str = openshell_core::driver_utils::UPSTREAM_PROXY_AUTH_MOUNT_PATH; @@ -201,14 +197,10 @@ pub fn resolver_secret_name(sandbox_id: &str) -> String { format!("{RESOLVER_SECRET_PREFIX}{sandbox_id}") } -/// Build per-sandbox Podman secret names for TLS CA, cert, and key. +/// Build the per-sandbox Podman secret name for the gateway CA. #[must_use] -pub fn tls_secret_names(sandbox_id: &str) -> [String; 3] { - [ - format!("{TLS_CA_SECRET_PREFIX}{sandbox_id}"), - format!("{TLS_CERT_SECRET_PREFIX}{sandbox_id}"), - format!("{TLS_KEY_SECRET_PREFIX}{sandbox_id}"), - ] +pub fn tls_secret_names(sandbox_id: &str) -> [String; 1] { + [format!("{TLS_CA_SECRET_PREFIX}{sandbox_id}")] } /// Truncate a container ID to 12 characters (standard short form). @@ -594,22 +586,12 @@ fn build_env( openshell_core::sandbox_env::POLICY_DNS_TRANSPARENT_TCP_CAPABILITY.into(), ); - // 3. TLS client cert paths (when mTLS is enabled). These point to - // the container-side mount paths where the cert files are - // bind-mounted from the host. + // 3. Gateway CA path (when TLS is enabled). if config.tls_enabled() { env.insert( openshell_core::sandbox_env::TLS_CA.into(), TLS_CA_MOUNT_PATH.into(), ); - env.insert( - openshell_core::sandbox_env::TLS_CERT.into(), - TLS_CERT_MOUNT_PATH.into(), - ); - env.insert( - openshell_core::sandbox_env::TLS_KEY.into(), - TLS_KEY_MOUNT_PATH.into(), - ); } if let Some(socket_path) = provider_spiffe_workload_api_socket_env_value(config) { @@ -1091,7 +1073,7 @@ pub fn build_container_spec_for_image( image_id: &str, oci_user: &str, supervisor_bin_path: Option<&Path>, - tls_secret_names: Option<&[String; 3]>, + tls_secret_names: Option<&[String; 1]>, ) -> Result { serde_json::to_value(build_base_spec( sandbox, @@ -1117,7 +1099,7 @@ fn build_base_spec( image_id: &str, oci_user: &str, supervisor_bin_path: Option<&Path>, - tls_secret_names: Option<&[String; 3]>, + tls_secret_names: Option<&[String; 1]>, ) -> Result { let name = container_name(&sandbox.workspace, &sandbox.name, &sandbox.id); let vol = volume_name(&sandbox.id); @@ -1259,7 +1241,7 @@ fn build_base_spec( mode: 0o400, }); } - if let Some([ca, cert, key]) = tls_secret_names { + if let Some([ca]) = tls_secret_names { secrets.push(SecretMount { source: ca.clone(), target: TLS_CA_MOUNT_PATH.into(), @@ -1267,20 +1249,6 @@ fn build_base_spec( gid: 0, mode: 0o400, }); - secrets.push(SecretMount { - source: cert.clone(), - target: TLS_CERT_MOUNT_PATH.into(), - uid: 0, - gid: 0, - mode: 0o400, - }); - secrets.push(SecretMount { - source: key.clone(), - target: TLS_KEY_MOUNT_PATH.into(), - uid: 0, - gid: 0, - mode: 0o400, - }); } secrets }, @@ -1317,18 +1285,14 @@ fn build_base_spec( destination: openshell_core::container_paths::NETNS_MOUNT_ROOT.into(), options: vec!["rw".into(), "nosuid".into(), "nodev".into()], }]; - // Deliver client TLS materials into the container when mTLS is + // Deliver the gateway CA into the container when TLS is // enabled. When userns remaps UIDs (auto, no-map), bind-mounted // host files are unreadable because the container root maps to a // different host UID. In that case TLS materials are delivered as // Podman secrets (handled in the `secrets` block above); otherwise // use bind mounts. if tls_secret_names.is_none() - && let (Some(ca), Some(cert), Some(key)) = ( - &config.guest_tls_ca, - &config.guest_tls_cert, - &config.guest_tls_key, - ) + && let Some(ca) = &config.guest_tls_ca { let mut ro = vec!["ro".into(), "rbind".into()]; if is_selinux_enabled() { @@ -1340,18 +1304,6 @@ fn build_base_spec( destination: TLS_CA_MOUNT_PATH.into(), options: ro.clone(), }); - m.push(Mount { - kind: "bind".into(), - source: cert.display().to_string(), - destination: TLS_CERT_MOUNT_PATH.into(), - options: ro.clone(), - }); - m.push(Mount { - kind: "bind".into(), - source: key.display().to_string(), - destination: TLS_KEY_MOUNT_PATH.into(), - options: ro, - }); } // Bind-mount the corporate proxy CA bundle read-only when // configured. A CA certificate is not secret, so unlike the proxy @@ -1452,7 +1404,7 @@ pub struct IsolationSpecInput<'a> { pub image_user: &'a str, pub image_env: &'a [String], pub supervisor_bin: Option<&'a Path>, - pub tls_secrets: Option<&'a [String; 3]>, + pub tls_secrets: Option<&'a [String; 1]>, pub identity: &'a openshell_isolation_interface::contract::ResolvedWorkloadIdentity, /// Whether this workload is created by a rootless Podman service. pub rootless: bool, @@ -1693,11 +1645,7 @@ pub fn build_isolation_specs( fn trusted_mount(destination: &str) -> bool { matches!( destination, - TLS_CA_MOUNT_PATH - | TLS_CERT_MOUNT_PATH - | TLS_KEY_MOUNT_PATH - | PROXY_CA_MOUNT_PATH - | PROVIDER_SPIFFE_WORKLOAD_API_SOCKET_MOUNT_DIR + TLS_CA_MOUNT_PATH | PROXY_CA_MOUNT_PATH | PROVIDER_SPIFFE_WORKLOAD_API_SOCKET_MOUNT_DIR ) || destination == openshell_core::container_paths::NETNS_MOUNT_ROOT } @@ -3382,12 +3330,10 @@ mod tests { } #[test] - fn container_spec_includes_tls_mounts_when_configured() { + fn container_spec_includes_only_tls_ca_when_configured() { let sandbox = test_sandbox("tls-id", "tls-name"); let mut config = test_config(); config.guest_tls_ca = Some(std::path::PathBuf::from("/host/ca.crt")); - config.guest_tls_cert = Some(std::path::PathBuf::from("/host/tls.crt")); - config.guest_tls_key = Some(std::path::PathBuf::from("/host/tls.key")); let spec = build_container_spec(&sandbox, &config); @@ -3397,16 +3343,10 @@ mod tests { env_map.get("OPENSHELL_TLS_CA").and_then(|v| v.as_str()), Some("/etc/openshell/tls/client/ca.crt"), ); - assert_eq!( - env_map.get("OPENSHELL_TLS_CERT").and_then(|v| v.as_str()), - Some("/etc/openshell/tls/client/tls.crt"), - ); - assert_eq!( - env_map.get("OPENSHELL_TLS_KEY").and_then(|v| v.as_str()), - Some("/etc/openshell/tls/client/tls.key"), - ); + assert!(env_map.get("OPENSHELL_TLS_CERT").is_none()); + assert!(env_map.get("OPENSHELL_TLS_KEY").is_none()); - // Verify bind mounts exist for all three cert files. + // Verify only the CA bind mount exists. let mounts = spec["mounts"] .as_array() .expect("mounts should be an array"); @@ -3419,14 +3359,7 @@ mod tests { bind_dests.contains(&"/etc/openshell/tls/client/ca.crt"), "should bind-mount CA cert" ); - assert!( - bind_dests.contains(&"/etc/openshell/tls/client/tls.crt"), - "should bind-mount client cert" - ); - assert!( - bind_dests.contains(&"/etc/openshell/tls/client/tls.key"), - "should bind-mount client key" - ); + assert_eq!(bind_dests.len(), 1); // Verify SELinux relabel option is present iff SELinux is enabled. let tls_binds: Vec<&Value> = mounts diff --git a/crates/openshell-driver-podman/src/driver.rs b/crates/openshell-driver-podman/src/driver.rs index 3788c756f4..d3023cddd8 100644 --- a/crates/openshell-driver-podman/src/driver.rs +++ b/crates/openshell-driver-podman/src/driver.rs @@ -297,13 +297,9 @@ async fn cleanup_sandbox_proxy_auth_secret(client: &PodmanClient, secret_name: & async fn create_tls_secrets( client: &PodmanClient, config: &PodmanComputeConfig, - names: &[String; 3], + names: &[String; 1], ) -> Result<(), ComputeDriverError> { - let paths = [ - config.guest_tls_ca.as_deref(), - config.guest_tls_cert.as_deref(), - config.guest_tls_key.as_deref(), - ]; + let paths = [config.guest_tls_ca.as_deref()]; let mut created = 0usize; for (name, path) in names.iter().zip(paths.iter()) { let Some(p) = path else { continue }; @@ -328,7 +324,7 @@ async fn create_tls_secrets( Ok(()) } -async fn cleanup_tls_secrets(client: &PodmanClient, names: &[String; 3]) { +async fn cleanup_tls_secrets(client: &PodmanClient, names: &[String]) { for name in names { if let Err(err) = client.remove_secret(name).await { warn!( @@ -2687,8 +2683,6 @@ mod tests { let cfg = PodmanComputeConfig { gateway_port: 8080, guest_tls_ca: Some(PathBuf::from("/tls/ca.crt")), - guest_tls_cert: Some(PathBuf::from("/tls/tls.crt")), - guest_tls_key: Some(PathBuf::from("/tls/tls.key")), ..PodmanComputeConfig::default() }; assert_eq!( @@ -2698,26 +2692,14 @@ mod tests { } #[test] - fn partial_tls_config_returns_error() { + fn ca_only_tls_config_is_enabled() { let cfg = PodmanComputeConfig { gateway_port: 8080, guest_tls_ca: Some(PathBuf::from("/tls/ca.crt")), - // guest_tls_cert and guest_tls_key not set — incomplete TLS config. ..PodmanComputeConfig::default() }; - assert!(!cfg.tls_enabled()); - let err = cfg - .validate_tls_config() - .expect_err("partial TLS config should be rejected"); - let msg = err.to_string(); - assert!( - msg.contains("OPENSHELL_PODMAN_TLS_CERT"), - "error should name the missing cert: {msg}" - ); - assert!( - msg.contains("OPENSHELL_PODMAN_TLS_KEY"), - "error should name the missing key: {msg}" - ); + assert!(cfg.tls_enabled()); + cfg.validate_tls_config().expect("CA-only TLS is valid"); } #[test] diff --git a/crates/openshell-driver-podman/src/main.rs b/crates/openshell-driver-podman/src/main.rs index d04b20cea7..8287ad019e 100644 --- a/crates/openshell-driver-podman/src/main.rs +++ b/crates/openshell-driver-podman/src/main.rs @@ -117,15 +117,15 @@ struct Args { #[arg(long, env = "OPENSHELL_SUPERVISOR_IMAGE")] supervisor_image: Option, - /// Host path to the CA certificate for sandbox mTLS. + /// Host path to the CA certificate for supervisor-to-gateway TLS. #[arg(long, env = "OPENSHELL_PODMAN_TLS_CA")] podman_tls_ca: Option, - /// Host path to the client certificate for sandbox mTLS. + /// Deprecated; client certificates are rejected. #[arg(long, env = "OPENSHELL_PODMAN_TLS_CERT")] podman_tls_cert: Option, - /// Host path to the client private key for sandbox mTLS. + /// Deprecated; client private keys are rejected. #[arg(long, env = "OPENSHELL_PODMAN_TLS_KEY")] podman_tls_key: Option, diff --git a/crates/openshell-driver-vm/README.md b/crates/openshell-driver-vm/README.md index b2103a6957..a0d83e1e4d 100644 --- a/crates/openshell-driver-vm/README.md +++ b/crates/openshell-driver-vm/README.md @@ -167,8 +167,6 @@ Select the VM driver with `--compute-driver vm`, `OPENSHELL_COMPUTE_DRIVER=vm`, | `overlay_disk_mib` | `4096` | Sparse writable overlay disk size per sandbox, in MiB. | | `krun_log_level` | `1` | libkrun verbosity (0-5). | | `guest_tls_ca` | unset | Historical key name for the host supervisor's gateway CA certificate. Required when `grpc_endpoint` uses `https://`; never copied into the guest. | -| `guest_tls_cert` | unset | Historical key name for the host supervisor's client certificate; never copied into the guest. | -| `guest_tls_key` | unset | Historical key name for the host supervisor's client private key; never copied into the guest. | | `https_proxy` | unset | Corporate forward proxy (`http://host:port` or `https://host:port`) that host control chains policy-approved TLS CONNECT egress through. Host-loopback proxy URLs work because control runs on the gateway host. | | `no_proxy` | unset | Comma-separated bypass list for the corporate proxy only. OpenShell policy evaluation still applies. | | `proxy_auth_file` | unset | Gateway-host path to a validated `user:pass` credential file. Staged root-only into the per-sandbox overlay and removed with the sandbox; credentials never enter logs or process arguments. | diff --git a/crates/openshell-driver-vm/src/driver.rs b/crates/openshell-driver-vm/src/driver.rs index 58ad21892e..3fceaf0e80 100644 --- a/crates/openshell-driver-vm/src/driver.rs +++ b/crates/openshell-driver-vm/src/driver.rs @@ -157,10 +157,6 @@ const GUEST_SSH_SOCKET_PATH: &str = openshell_core::container_paths::SSH_SOCKET_ #[allow(dead_code)] const GUEST_TLS_CA_PATH: &str = openshell_core::container_paths::VM_GUEST_TLS_CA_PATH; #[allow(dead_code)] -const GUEST_TLS_CERT_PATH: &str = openshell_core::container_paths::VM_GUEST_TLS_CERT_PATH; -#[allow(dead_code)] -const GUEST_TLS_KEY_PATH: &str = openshell_core::container_paths::VM_GUEST_TLS_KEY_PATH; -#[allow(dead_code)] const GUEST_SANDBOX_TOKEN_PATH: &str = openshell_core::container_paths::VM_GUEST_SANDBOX_TOKEN_PATH; const GUEST_INIT_DROPIN_DIR: &str = openshell_core::container_paths::VM_GUEST_INIT_DROPIN_DIR; const GUEST_BOUNDARY_CONFIG_DIR: &str = "/.openshell/state"; @@ -215,8 +211,6 @@ static OWNER_STATE_WRITE_COUNTER: AtomicU64 = AtomicU64::new(0); #[derive(Debug, Clone)] struct VmDriverTlsPaths { ca: PathBuf, - cert: PathBuf, - key: PathBuf, } #[derive(Debug, Clone)] @@ -497,15 +491,16 @@ impl VmDriverConfig { } fn tls_paths(&self) -> Result, String> { - let provided = [ - self.guest_tls_ca.as_ref(), - self.guest_tls_cert.as_ref(), - self.guest_tls_key.as_ref(), - ]; - if provided.iter().all(Option::is_none) { + if self.guest_tls_cert.is_some() || self.guest_tls_key.is_some() { + return Err( + "sandbox client certificates are no longer supported; remove OPENSHELL_VM_TLS_CERT and OPENSHELL_VM_TLS_KEY" + .to_string(), + ); + } + if self.guest_tls_ca.is_none() { return if self.requires_tls_materials() { Err( - "https:// openshell endpoint requires OPENSHELL_VM_TLS_CA, OPENSHELL_VM_TLS_CERT, and OPENSHELL_VM_TLS_KEY so the host supervisor can authenticate to the gateway" + "https:// openshell endpoint requires OPENSHELL_VM_TLS_CA so the host supervisor can authenticate the gateway" .to_string(), ) } else { @@ -518,18 +513,7 @@ impl VmDriverConfig { "OPENSHELL_VM_TLS_CA is required when TLS materials are configured".to_string(), ); }; - let Some(cert) = self.guest_tls_cert.clone() else { - return Err( - "OPENSHELL_VM_TLS_CERT is required when TLS materials are configured".to_string(), - ); - }; - let Some(key) = self.guest_tls_key.clone() else { - return Err( - "OPENSHELL_VM_TLS_KEY is required when TLS materials are configured".to_string(), - ); - }; - - for path in [&ca, &cert, &key] { + for path in [&ca] { if !path.is_file() { return Err(format!( "TLS material '{}' does not exist or is not a file", @@ -538,7 +522,7 @@ impl VmDriverConfig { } } - Ok(Some(VmDriverTlsPaths { ca, cert, key })) + Ok(Some(VmDriverTlsPaths { ca })) } } @@ -983,10 +967,7 @@ impl VmDriver { } configure_main_exit_marker(&mut command, state_dir); if let Some(tls) = tls_paths { - command - .env(openshell_core::sandbox_env::TLS_CA, &tls.ca) - .env(openshell_core::sandbox_env::TLS_CERT, &tls.cert) - .env(openshell_core::sandbox_env::TLS_KEY, &tls.key); + command.env(openshell_core::sandbox_env::TLS_CA, &tls.ca); } #[cfg(unix)] let (liveness_read, liveness_write) = nix::unistd::pipe().map_err(|error| { @@ -9758,8 +9739,6 @@ mod tests { let config = VmDriverConfig { grpc_endpoint: "https://127.0.0.1:8443".to_string(), guest_tls_ca: Some(PathBuf::from("/host/ca.crt")), - guest_tls_cert: Some(PathBuf::from("/host/tls.crt")), - guest_tls_key: Some(PathBuf::from("/host/tls.key")), ..Default::default() }; let sandbox = Sandbox { diff --git a/crates/openshell-driver-vm/src/main.rs b/crates/openshell-driver-vm/src/main.rs index fe0b035023..d7195b4cc3 100644 --- a/crates/openshell-driver-vm/src/main.rs +++ b/crates/openshell-driver-vm/src/main.rs @@ -119,9 +119,11 @@ struct Args { #[arg(long = "guest-tls-ca", env = "OPENSHELL_VM_TLS_CA")] guest_tls_ca: Option, + /// Deprecated; client certificates are rejected. #[arg(long = "guest-tls-cert", env = "OPENSHELL_VM_TLS_CERT")] guest_tls_cert: Option, + /// Deprecated; client private keys are rejected. #[arg(long = "guest-tls-key", env = "OPENSHELL_VM_TLS_KEY")] guest_tls_key: Option, diff --git a/crates/openshell-gateway/src/lib.rs b/crates/openshell-gateway/src/lib.rs index 8864e85ede..376e80380b 100644 --- a/crates/openshell-gateway/src/lib.rs +++ b/crates/openshell-gateway/src/lib.rs @@ -187,7 +187,6 @@ fn install_in_tree_compute_drivers(registry: &mut ComputeDriverRegistry) { .map(|registration| { registration .with_telemetry_category(TelemetryComputeDriver::anonymous_category("kubernetes")) - .without_mtls_user_auth() .with_in_process_tracing(openshell_driver_kubernetes::otel_tracing::TRACING) }), #[cfg(feature = "compute-driver-podman")] @@ -307,12 +306,7 @@ impl openshell_server::ComputeDriverFactory for DockerFactory { ) -> openshell_core::Result { let mut config: openshell_driver_docker::DockerComputeConfig = context.driver_config()?; require_guest_tls_for_local_driver(&context, "docker")?; - apply_guest_tls( - &mut config.guest_tls_ca, - &mut config.guest_tls_cert, - &mut config.guest_tls_key, - context.guest_tls_paths(), - ); + apply_guest_tls(&mut config.guest_tls_ca, context.guest_tls_ca()); let driver = openshell_driver_docker::DockerComputeDriver::new( context.gateway_bind_address(), context.gateway_log_level(), @@ -353,12 +347,7 @@ impl openshell_server::ComputeDriverFactory for PodmanFactory { ) -> openshell_core::Result { let mut config = podman_config(context.config_context())?; require_guest_tls_for_local_driver(&context, "podman")?; - apply_guest_tls( - &mut config.guest_tls_ca, - &mut config.guest_tls_cert, - &mut config.guest_tls_key, - context.guest_tls_paths(), - ); + apply_guest_tls(&mut config.guest_tls_ca, context.guest_tls_ca()); let driver = openshell_driver_podman::PodmanComputeDriver::new(config) .await .map_err(|error| openshell_core::Error::execution(error.to_string()))?; @@ -421,7 +410,7 @@ impl openshell_server::ComputeDriverFactory for VmFactory { let mut config = vm_config(context.config_context())?; require_guest_tls_for_local_driver(&context, "vm")?; if config.grpc_endpoint.trim().is_empty() - && (!context.gateway_tls_enabled() || context.guest_tls_paths().is_some()) + && (!context.gateway_tls_enabled() || context.guest_tls_ca().is_some()) { let scheme = if context.gateway_tls_enabled() { "https" @@ -430,12 +419,7 @@ impl openshell_server::ComputeDriverFactory for VmFactory { }; config.grpc_endpoint = format!("{scheme}://127.0.0.1:{}", context.gateway_port()); } - apply_guest_tls( - &mut config.guest_tls_ca, - &mut config.guest_tls_cert, - &mut config.guest_tls_key, - context.guest_tls_paths(), - ); + apply_guest_tls(&mut config.guest_tls_ca, context.guest_tls_ca()); let endpoint = vm::spawn( context.gateway_log_level(), context.gateway_name(), @@ -474,7 +458,7 @@ fn require_guest_tls_for_local_driver( ) -> openshell_core::Result<()> { validate_local_driver_guest_tls( context.gateway_tls_enabled(), - context.guest_tls_paths().is_some(), + context.guest_tls_ca().is_some(), driver_name, ) } @@ -494,7 +478,7 @@ fn validate_local_driver_guest_tls( ) -> openshell_core::Result<()> { if gateway_tls_enabled && !has_guest_tls { return Err(openshell_core::Error::config(format!( - "gateway TLS requires guest_tls_ca, guest_tls_cert, and guest_tls_key in [openshell.gateway] when using the {driver_name} compute driver" + "gateway TLS requires guest_tls_ca in [openshell.gateway] when using the {driver_name} compute driver" ))); } Ok(()) @@ -508,20 +492,11 @@ fn validate_local_driver_guest_tls( feature = "compute-driver-vm" ) ))] -fn apply_guest_tls( - ca: &mut Option, - cert: &mut Option, - key: &mut Option, - defaults: Option<(&std::path::Path, &std::path::Path, &std::path::Path)>, -) { +fn apply_guest_tls(ca: &mut Option, default_ca: Option<&std::path::Path>) { if ca.is_none() - && cert.is_none() - && key.is_none() - && let Some((default_ca, default_cert, default_key)) = defaults + && let Some(default_ca) = default_ca { *ca = Some(default_ca.to_owned()); - *cert = Some(default_cert.to_owned()); - *key = Some(default_key.to_owned()); } } @@ -550,7 +525,7 @@ mod local_driver_tests { } #[test] - fn tls_enabled_local_drivers_require_a_guest_bundle() { + fn tls_enabled_local_drivers_require_a_gateway_ca() { for driver_name in ["docker", "podman", "vm"] { let error = validate_local_driver_guest_tls(true, false, driver_name) .expect_err("TLS-enabled local driver must require guest TLS"); @@ -559,29 +534,16 @@ mod local_driver_tests { assert!(message.contains("guest_tls_ca")); } validate_local_driver_guest_tls(true, true, "docker") - .expect("a complete guest bundle satisfies the requirement"); + .expect("a gateway CA satisfies the requirement"); validate_local_driver_guest_tls(false, false, "docker") .expect("plaintext gateways do not require guest TLS"); } #[test] - fn package_managed_guest_bundle_is_injected_when_driver_paths_are_absent() { + fn package_managed_gateway_ca_is_injected_when_driver_path_is_absent() { let mut ca = None; - let mut cert = None; - let mut key = None; - apply_guest_tls( - &mut ca, - &mut cert, - &mut key, - Some(( - Path::new("/managed/ca.pem"), - Path::new("/managed/client.pem"), - Path::new("/managed/client-key.pem"), - )), - ); + apply_guest_tls(&mut ca, Some(Path::new("/managed/ca.pem"))); assert_eq!(ca, Some(PathBuf::from("/managed/ca.pem"))); - assert_eq!(cert, Some(PathBuf::from("/managed/client.pem"))); - assert_eq!(key, Some(PathBuf::from("/managed/client-key.pem"))); } } diff --git a/crates/openshell-gateway/src/vm.rs b/crates/openshell-gateway/src/vm.rs index b0c6958ca0..d0c00cd786 100644 --- a/crates/openshell-gateway/src/vm.rs +++ b/crates/openshell-gateway/src/vm.rs @@ -105,15 +105,9 @@ pub struct VmComputeConfig { /// Maximum accepted rootfs tar size, in bytes, before and after decompression. pub rootfs_tar_max_bytes: Option, - /// Host-side CA certificate for the guest's mTLS client bundle. + /// Host-side CA certificate used to authenticate the gateway. pub guest_tls_ca: Option, - /// Host-side client certificate for the guest's mTLS client bundle. - pub guest_tls_cert: Option, - - /// Host-side private key for the guest's mTLS client bundle. - pub guest_tls_key: Option, - /// Corporate forward-proxy settings passed to the VM driver. Flattening /// preserves the shared local-driver TOML field names. #[serde(flatten)] @@ -256,8 +250,6 @@ impl Default for VmComputeConfig { rootfs_tar_staging_dir: None, rootfs_tar_max_bytes: None, guest_tls_ca: None, - guest_tls_cert: None, - guest_tls_key: None, upstream_proxy: UpstreamProxyConfig::default(), proxy_ca_bundle: None, provider_spiffe_workload_api_tcp_endpoint: None, @@ -270,8 +262,6 @@ impl Default for VmComputeConfig { #[derive(Debug, Clone, PartialEq, Eq)] pub struct VmGuestTlsPaths { pub ca: PathBuf, - pub cert: PathBuf, - pub key: PathBuf, } /// Resolve the `openshell-driver-vm` binary path. @@ -504,14 +494,9 @@ pub fn compute_driver_guest_tls_paths( return Ok(None); } - let provided = [ - vm_config.guest_tls_ca.as_ref(), - vm_config.guest_tls_cert.as_ref(), - vm_config.guest_tls_key.as_ref(), - ]; - if provided.iter().all(Option::is_none) { + if vm_config.guest_tls_ca.is_none() { return Err(Error::config( - "vm compute driver requires guest_tls_ca, guest_tls_cert, and guest_tls_key when grpc_endpoint uses https://", + "vm compute driver requires guest_tls_ca when grpc_endpoint uses https://", )); } @@ -520,18 +505,7 @@ pub fn compute_driver_guest_tls_paths( "guest_tls_ca is required when VM guest TLS materials are configured", )); }; - let Some(cert) = vm_config.guest_tls_cert.clone() else { - return Err(Error::config( - "guest_tls_cert is required when VM guest TLS materials are configured", - )); - }; - let Some(key) = vm_config.guest_tls_key.clone() else { - return Err(Error::config( - "guest_tls_key is required when VM guest TLS materials are configured", - )); - }; - - for path in [&ca, &cert, &key] { + for path in [&ca] { if !path.is_file() { return Err(Error::config(format!( "vm guest TLS material '{}' does not exist or is not a file", @@ -540,7 +514,7 @@ pub fn compute_driver_guest_tls_paths( } } - Ok(Some(VmGuestTlsPaths { ca, cert, key })) + Ok(Some(VmGuestTlsPaths { ca })) } /// Launch the VM compute-driver subprocess, wait for its UDS to come up, @@ -599,8 +573,6 @@ pub async fn spawn( append_vm_rootfs_tar_args(&mut command, vm_config); if let Some(tls) = guest_tls_paths { command.arg("--guest-tls-ca").arg(tls.ca); - command.arg("--guest-tls-cert").arg(tls.cert); - command.arg("--guest-tls-key").arg(tls.key); } append_vm_proxy_and_spiffe_args(&mut command, vm_config); @@ -1105,47 +1077,26 @@ mod tests { }; let err = compute_driver_guest_tls_paths(&vm_config) - .expect_err("https vm endpoints should require an explicit guest client bundle"); - assert!( - err.to_string() - .contains("guest_tls_ca, guest_tls_cert, and guest_tls_key") - ); + .expect_err("https vm endpoints should require an explicit gateway CA"); + assert!(err.to_string().contains("guest_tls_ca")); } #[test] - fn vm_compute_driver_tls_uses_guest_bundle_not_gateway_server_identity() { + fn vm_compute_driver_tls_uses_only_gateway_ca() { let dir = tempdir().unwrap(); - let server_cert = dir.path().join("server.crt"); - let server_key = dir.path().join("server.key"); let guest_ca = dir.path().join("guest-ca.crt"); - let guest_cert = dir.path().join("guest.crt"); - let guest_key = dir.path().join("guest.key"); - for path in [ - &server_cert, - &server_key, - &guest_ca, - &guest_cert, - &guest_key, - ] { - std::fs::write(path, path.display().to_string()).unwrap(); - } + std::fs::write(&guest_ca, guest_ca.display().to_string()).unwrap(); let vm_config = VmComputeConfig { grpc_endpoint: "https://gateway.internal:8443".to_string(), guest_tls_ca: Some(guest_ca.clone()), - guest_tls_cert: Some(guest_cert.clone()), - guest_tls_key: Some(guest_key.clone()), ..Default::default() }; let guest_paths = compute_driver_guest_tls_paths(&vm_config) .unwrap() - .expect("https vm endpoints should pass an explicit guest client bundle"); + .expect("https vm endpoints should pass an explicit gateway CA"); assert_eq!(guest_paths.ca, guest_ca); - assert_eq!(guest_paths.cert, guest_cert); - assert_eq!(guest_paths.key, guest_key); - assert_ne!(guest_paths.cert, server_cert); - assert_ne!(guest_paths.key, server_key); } #[test] diff --git a/crates/openshell-server/src/auth/principal.rs b/crates/openshell-server/src/auth/principal.rs index 390e4861eb..298533278a 100644 --- a/crates/openshell-server/src/auth/principal.rs +++ b/crates/openshell-server/src/auth/principal.rs @@ -78,9 +78,6 @@ pub enum SandboxIdentitySource { /// Generation-bound gateway JWT validated against the persisted runtime /// identity by [`super::sandbox_jwt::SandboxSessionJwtAuthenticator`]. BootstrapJwt { issuer: String }, - /// Per-sandbox client certificate. Reserved for channel-bound sandbox - /// identity. - BootstrapCert { fingerprint: String }, /// Driver-native credential used to bootstrap a gateway-minted JWT via /// `IssueSandboxToken`. The named compute driver authenticated only the /// sandbox identity and its concrete runtime binding; the gateway still diff --git a/crates/openshell-server/src/cli.rs b/crates/openshell-server/src/cli.rs index 9000ab1324..87b67f5e8a 100644 --- a/crates/openshell-server/src/cli.rs +++ b/crates/openshell-server/src/cli.rs @@ -180,10 +180,9 @@ struct RunArgs { )] oidc_jwks_allowed_origins: Vec, - /// Enable mTLS client certificate authentication for local single-user gateways. + /// Enable mTLS client certificate authentication for gateway users. /// - /// When unset, this defaults on for drivers registered as local - /// single-player backends when client certificate verification is + /// When unset, this defaults on when client certificate verification is /// configured and no OIDC issuer is present. #[arg( long = "enable-mtls-auth", @@ -357,8 +356,6 @@ fn prepare_server_config_with_drivers( let compute_driver = compute_drivers .select(args.compute_driver.as_deref()) .map_err(|error| miette::miette!("{error}"))?; - let selected_registration = compute_drivers.get(compute_driver.name()); - let local_tls = apply_runtime_defaults(args)?; let guest_tls = GuestTlsPaths::resolve( file.as_ref().map(|file| &file.openshell.gateway), @@ -371,9 +368,7 @@ fn prepare_server_config_with_drivers( let bind = SocketAddr::new(args.bind_address, args.port); let has_client_ca = args.tls_client_ca.is_some(); - let has_oidc = args.oidc_issuer.is_some(); - let mtls_auth_enabled = - resolve_mtls_auth_enabled(args, matches, file.as_ref(), selected_registration); + let mtls_auth_enabled = resolve_mtls_auth_enabled(args, matches, file.as_ref()); if args.disable_tls && has_client_ca { return Err(miette::miette!( @@ -390,14 +385,6 @@ fn prepare_server_config_with_drivers( "mTLS user authentication requires --tls-client-ca so client certificates can be verified." )); } - if mtls_auth_enabled - && selected_registration.is_some_and(|registration| !registration.supports_mtls_user_auth()) - { - return Err(miette::miette!( - "mTLS user authentication is not supported with the selected compute driver. Configure OIDC or a trusted fronting proxy for user authentication." - )); - } - let tls = if args.disable_tls { None } else { @@ -425,7 +412,11 @@ fn prepare_server_config_with_drivers( Some(openshell_core::TlsConfig { cert_path, key_path, - require_client_auth: has_client_ca && !has_oidc, + // Sandboxes authenticate at the application layer with bearer + // identity, so TLS must permit clients without certificates. + // When present, CLI certificates are still verified and may be + // promoted to users by the independently configured mTLS policy. + require_client_auth: false, client_ca_path: args.tls_client_ca.clone(), external_cert_path: ext_cert, external_key_path: ext_key, @@ -830,12 +821,9 @@ fn run_effective_config_preflight( .map(|driver| compute_drivers.select(Some(driver))) .transpose() .map_err(|error| miette::miette!("{error}"))?; - let selected_registration = selection - .as_ref() - .and_then(|selection| compute_drivers.get(selection.name())); let empty_file = ConfigFile::default(); let semantic_file = file.as_ref().unwrap_or(&empty_file); - validate_preflight_semantics(&run, matches, semantic_file, selected_registration)?; + validate_preflight_semantics(&run, matches, semantic_file)?; let mut endpoint_overrides = BTreeMap::new(); if let Some(selection) = selection.as_ref() @@ -898,7 +886,6 @@ fn validate_preflight_semantics( args: &RunArgs, matches: &ArgMatches, file: &ConfigFile, - selected_registration: Option<&crate::ComputeDriverRegistration>, ) -> Result<()> { let gateway = &file.openshell.gateway; validate_grpc_rate_limit_args( @@ -909,8 +896,7 @@ fn validate_preflight_semantics( .map_err(|error| miette::miette!("invalid gateway guest TLS configuration: {error}"))?; let has_client_ca = args.tls_client_ca.is_some(); - let mtls_auth_enabled = - resolve_mtls_auth_enabled(args, matches, Some(file), selected_registration); + let mtls_auth_enabled = resolve_mtls_auth_enabled(args, matches, Some(file)); if args.disable_tls && has_client_ca { return Err(miette::miette!( "--disable-tls and --tls-client-ca are mutually exclusive" @@ -924,13 +910,6 @@ fn validate_preflight_semantics( "mTLS user authentication requires --tls-client-ca" )); } - if mtls_auth_enabled - && selected_registration.is_some_and(|registration| !registration.supports_mtls_user_auth()) - { - return Err(miette::miette!( - "mTLS user authentication is not supported with the selected compute driver" - )); - } if !args.disable_tls && args.tls_cert.is_some() != args.tls_key.is_some() { return Err(miette::miette!( "gateway TLS requires both --tls-cert and --tls-key" @@ -1240,15 +1219,10 @@ fn normalize_compute_driver_socket_args(args: &mut RunArgs) -> Result<()> { Ok(()) } -fn is_singleplayer_driver(registration: Option<&crate::ComputeDriverRegistration>) -> bool { - registration.is_some_and(crate::ComputeDriverRegistration::is_local_singleplayer) -} - fn resolve_mtls_auth_enabled( args: &RunArgs, matches: &ArgMatches, file: Option<&ConfigFile>, - selected_registration: Option<&crate::ComputeDriverRegistration>, ) -> bool { let file_configured = file .and_then(|f| f.openshell.gateway.mtls_auth.as_ref()) @@ -1261,7 +1235,7 @@ fn resolve_mtls_auth_enabled( return false; } - is_singleplayer_driver(selected_registration) + true } #[cfg(test)] @@ -1345,15 +1319,12 @@ mod tests { } } - fn test_registry(name: &str, singleplayer: bool, mtls: bool) -> crate::ComputeDriverRegistry { + fn test_registry(name: &str, singleplayer: bool) -> crate::ComputeDriverRegistry { let mut registration = crate::ComputeDriverRegistration::new(name, 100, None, TestFactory).unwrap(); if singleplayer { registration = registration.with_local_singleplayer(); } - if !mtls { - registration = registration.without_mtls_user_auth(); - } let mut registry = crate::ComputeDriverRegistry::new(); registry.install(registration).unwrap(); registry @@ -1672,7 +1643,7 @@ mod tests { "sqlite::memory:", "--disable-tls", ]); - let registry = test_registry("podman", true, true); + let registry = test_registry("podman", true); let prepared = super::prepare_server_config_with_drivers(&mut args, &matches, ®istry).unwrap(); @@ -1958,7 +1929,7 @@ mod tests { let _canonical = EnvVarGuard::remove("OPENSHELL_COMPUTE_DRIVER"); let _legacy = EnvVarGuard::set("OPENSHELL_DRIVERS", "podman,docker"); let (run, matches) = parse_with_args(&["openshell-gateway"]); - let registry = test_registry("podman", true, true); + let registry = test_registry("podman", true); let error = super::run_config_preflight_with_drivers( super::ConfigPreflightArgs::default(), @@ -2147,7 +2118,7 @@ mod tests { } #[test] - fn config_preflight_applies_selected_driver_mtls_capability() { + fn config_preflight_allows_driver_independent_mtls() { let _lock = ENV_LOCK .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); @@ -2169,16 +2140,15 @@ mod tests { "--enable-mtls-auth", "true", ]); - let registry = test_registry("shared", false, false); + let registry = test_registry("shared", false); - let error = super::run_config_preflight_with_drivers( + super::run_config_preflight_with_drivers( super::ConfigPreflightArgs::default(), run, &matches, ®istry, ) - .expect_err("selected shared driver must reject mTLS user authentication"); - assert!(error.to_string().contains("not supported")); + .expect("gateway mTLS authentication is independent of the selected driver"); } #[test] @@ -2312,7 +2282,7 @@ mod tests { ), ( "guest-tls", - "[openshell]\nversion = 2\n[openshell.gateway]\nguest_tls_ca = '/tls/ca.pem'\n", + "[openshell]\nversion = 2\n[openshell.gateway]\nguest_tls_ca = '/tls/ca.pem'\ndisable_tls = true\n", ), ( "external-tls", @@ -2407,7 +2377,7 @@ mod tests { let path = dir.path().join("gateway.toml"); std::fs::write( &path, - "[openshell]\nversion = 2\n[openshell.gateway]\nguest_tls_ca = '/future/ca.pem'\nguest_tls_cert = '/future/client.pem'\nguest_tls_key = '/future/client-key.pem'\n", + "[openshell]\nversion = 2\n[openshell.gateway]\nguest_tls_ca = '/future/ca.pem'\n", ) .unwrap(); let (run, matches) = parse_with_args(&["openshell-gateway"]); @@ -2619,7 +2589,7 @@ mod tests { } #[test] - fn tls_client_certificate_requirement_is_derived_from_ca_and_oidc() { + fn tls_accepts_bearer_clients_with_and_without_oidc() { let _lock = ENV_LOCK .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); @@ -2627,9 +2597,9 @@ mod tests { let _config = EnvVarGuard::set("XDG_CONFIG_HOME", config_home.path().to_str().unwrap()); let _config_path = EnvVarGuard::remove("OPENSHELL_GATEWAY_CONFIG"); let _legacy = EnvVarGuard::remove("OPENSHELL_DRIVERS"); - let registry = test_registry("shared", false, false); + let registry = test_registry("shared", false); - for (oidc_issuer, expected) in [(None, true), (Some("https://idp.example.com"), false)] { + for (oidc_issuer, expected) in [(None, false), (Some("https://idp.example.com"), false)] { let mut startup_args = vec![ "openshell-gateway", "--db-url", @@ -2659,7 +2629,7 @@ mod tests { } #[test] - fn mtls_auth_auto_defaults_for_local_tls_driver() { + fn mtls_auth_auto_defaults_when_client_ca_is_configured() { let _lock = ENV_LOCK .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); @@ -2679,12 +2649,7 @@ mod tests { "/tmp/ca.crt", ]); - assert!(super::resolve_mtls_auth_enabled( - &args, - &matches, - None, - test_registry("local", true, true).get("local") - )); + assert!(super::resolve_mtls_auth_enabled(&args, &matches, None)); } #[test] @@ -2719,11 +2684,20 @@ mod tests { assert_eq!(prepared.compute_driver.name(), "local"); assert!(prepared.config.compute_driver.is_none()); assert!(prepared.config.mtls_auth.enabled); + assert!( + !prepared + .config + .tls + .as_ref() + .expect("TLS config") + .require_client_auth, + "sandbox bearer clients must be allowed through the TLS handshake" + ); assert_eq!(REGISTRY_DETECTION_CALLS.load(Ordering::SeqCst), 1); } #[test] - fn mtls_auth_does_not_auto_default_for_shared_driver() { + fn mtls_auth_default_is_driver_independent() { let _lock = ENV_LOCK .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); @@ -2743,12 +2717,7 @@ mod tests { "/tmp/ca.crt", ]); - assert!(!super::resolve_mtls_auth_enabled( - &args, - &matches, - None, - test_registry("shared", false, false).get("shared") - )); + assert!(super::resolve_mtls_auth_enabled(&args, &matches, None)); } #[test] @@ -2783,8 +2752,7 @@ enabled = false assert!(!super::resolve_mtls_auth_enabled( &args, &matches, - Some(&file), - test_registry("local", true, true).get("local") + Some(&file) )); } @@ -3089,15 +3057,6 @@ ssh_session_ttl_secs = 1234 assert_eq!(file.openshell.gateway.ssh_session_ttl_secs, Some(1234)); } - #[test] - fn singleplayer_behavior_comes_from_registration() { - let local = test_registry("local", true, true); - assert!(super::is_singleplayer_driver(local.get("local"))); - - let shared = test_registry("shared", false, true); - assert!(!super::is_singleplayer_driver(shared.get("shared"))); - } - #[test] fn compute_driver_socket_flag_uses_explicit_driver_name() { let _lock = ENV_LOCK diff --git a/crates/openshell-server/src/compute/driver_config.rs b/crates/openshell-server/src/compute/driver_config.rs index 6a2dfe44c2..20b52d9e34 100644 --- a/crates/openshell-server/src/compute/driver_config.rs +++ b/crates/openshell-server/src/compute/driver_config.rs @@ -17,56 +17,28 @@ use std::path::PathBuf; #[derive(Debug, Clone, PartialEq, Eq)] pub struct GuestTlsPaths { ca: PathBuf, - cert: PathBuf, - key: PathBuf, } impl GuestTlsPaths { - pub(crate) fn as_paths(&self) -> (&std::path::Path, &std::path::Path, &std::path::Path) { - (&self.ca, &self.cert, &self.key) + pub(crate) fn as_path(&self) -> &std::path::Path { + &self.ca } -} -impl GuestTlsPaths { - fn configured_paths( - gateway: &config_file::GatewayFileSection, - ) -> (Option<&PathBuf>, Option<&PathBuf>, Option<&PathBuf>) { - ( - gateway.guest_tls_ca.as_ref(), - gateway.guest_tls_cert.as_ref(), - gateway.guest_tls_key.as_ref(), - ) - } - - /// Validate guest TLS relationships without reading certificate files. + /// Validate gateway CA configuration without reading certificate files. pub(crate) fn validate_configuration( gateway: Option<&config_file::GatewayFileSection>, tls_disabled: bool, ) -> std::result::Result<(), String> { - let configured = gateway.map(Self::configured_paths); - let provided = configured - .is_some_and(|(ca, cert, key)| ca.is_some() || cert.is_some() || key.is_some()); - if tls_disabled && provided { - return Err( - "guest_tls_ca, guest_tls_cert, and guest_tls_key require gateway TLS; remove them or omit --disable-tls" - .to_string(), - ); - } - if let Some((ca, cert, key)) = configured - && (ca.is_some() || cert.is_some() || key.is_some()) - && (ca.is_none() || cert.is_none() || key.is_none()) - { + if tls_disabled && gateway.is_some_and(|gateway| gateway.guest_tls_ca.is_some()) { return Err( - "guest TLS requires one complete bundle: guest_tls_ca, guest_tls_cert, and guest_tls_key" - .to_string(), + "guest_tls_ca requires gateway TLS; remove it or omit --disable-tls".to_string(), ); } Ok(()) } - /// Resolve gateway-owned guest TLS inputs. Explicit TOML values take - /// precedence over the package-managed local bundle; partial bundles are - /// rejected before any driver is deserialized or constructed. + /// Explicit gateway CA configuration takes precedence over the + /// package-managed local CA. User client credentials stay on the host. pub(crate) fn resolve( gateway: Option<&config_file::GatewayFileSection>, local: Option<&LocalTlsPaths>, @@ -76,31 +48,17 @@ impl GuestTlsPaths { if tls_disabled { return Ok(None); } - - if let Some((Some(ca), Some(cert), Some(key))) = gateway.map(Self::configured_paths) { - for (field, path) in [ - ("guest_tls_ca", ca), - ("guest_tls_cert", cert), - ("guest_tls_key", key), - ] { - if !path.is_file() { - return Err(format!( - "{field} '{}' does not exist or is not a file", - path.display() - )); - } + if let Some(ca) = gateway.and_then(|gateway| gateway.guest_tls_ca.as_ref()) { + if !ca.is_file() { + return Err(format!( + "guest_tls_ca '{}' does not exist or is not a file", + ca.display() + )); } - return Ok(Some(Self { - ca: ca.clone(), - cert: cert.clone(), - key: key.clone(), - })); + return Ok(Some(Self { ca: ca.clone() })); } - Ok(local.map(|paths| Self { ca: paths.ca.clone(), - cert: paths.client_cert.clone(), - key: paths.client_key.clone(), })) } } @@ -198,18 +156,23 @@ where }) } -/// Reject TLS paths in gateway driver tables. These credentials are gateway -/// inputs and are injected only into the selected local driver after the -/// gateway has validated the complete bundle. +/// Reject TLS paths in gateway driver tables. The gateway CA is injected +/// into the selected local driver after gateway validation. fn reject_driver_owned_guest_tls_fields(table: &toml::Value) -> Result<()> { let Some(table) = table.as_table() else { return Ok(()); }; for field in ["guest_tls_ca", "guest_tls_cert", "guest_tls_key"] { if table.contains_key(field) { - return Err(Error::config(format!( - "{field} belongs in [openshell.gateway], not a [openshell.drivers.*] table" - ))); + let message = if field == "guest_tls_ca" { + "guest_tls_ca belongs in [openshell.gateway], not a [openshell.drivers.*] table" + .to_string() + } else { + format!( + "{field} is no longer supported; remove it because supervisors authenticate with bearer tokens" + ) + }; + return Err(Error::config(message)); } } Ok(()) @@ -291,18 +254,12 @@ mod tests { } #[test] - fn gateway_guest_tls_resolves_explicit_complete_bundle() { + fn gateway_guest_tls_resolves_explicit_ca() { let dir = tempfile::tempdir().expect("temp dir"); let ca = dir.path().join("ca.pem"); - let cert = dir.path().join("cert.pem"); - let key = dir.path().join("key.pem"); - for path in [&ca, &cert, &key] { - std::fs::write(path, b"test").expect("write TLS fixture"); - } + std::fs::write(&ca, b"test").expect("write TLS fixture"); let gateway = config_file::GatewayFileSection { guest_tls_ca: Some(ca.clone()), - guest_tls_cert: Some(cert.clone()), - guest_tls_key: Some(key.clone()), ..Default::default() }; @@ -310,33 +267,7 @@ mod tests { .expect("complete guest TLS should resolve") .expect("guest TLS bundle"); - assert_eq!( - resolved.as_paths(), - (ca.as_path(), cert.as_path(), key.as_path()) - ); - } - - #[test] - fn gateway_guest_tls_rejects_every_partial_bundle() { - let path = PathBuf::from("/tmp/guest-tls.pem"); - for (ca, cert, key) in [ - (Some(path.clone()), None, None), - (None, Some(path.clone()), None), - (None, None, Some(path.clone())), - (Some(path.clone()), Some(path.clone()), None), - (Some(path.clone()), None, Some(path.clone())), - (None, Some(path.clone()), Some(path)), - ] { - let gateway = config_file::GatewayFileSection { - guest_tls_ca: ca, - guest_tls_cert: cert, - guest_tls_key: key, - ..Default::default() - }; - let error = GuestTlsPaths::resolve(Some(&gateway), None, false) - .expect_err("partial guest TLS must fail"); - assert!(error.contains("one complete bundle")); - } + assert_eq!(resolved.as_path(), ca.as_path()); } #[test] @@ -344,8 +275,6 @@ mod tests { let dir = tempfile::tempdir().expect("temp dir"); let gateway = config_file::GatewayFileSection { guest_tls_ca: Some(dir.path().join("missing-ca.pem")), - guest_tls_cert: Some(dir.path().join("missing-cert.pem")), - guest_tls_key: Some(dir.path().join("missing-key.pem")), ..Default::default() }; let error = GuestTlsPaths::resolve(Some(&gateway), None, false) @@ -366,14 +295,7 @@ mod tests { let resolved = GuestTlsPaths::resolve(None, Some(&local), false) .expect("managed bundle should resolve") .expect("guest TLS bundle"); - assert_eq!( - resolved.as_paths(), - ( - Path::new("/managed/ca.pem"), - Path::new("/managed/client-cert.pem"), - Path::new("/managed/client-key.pem"), - ) - ); + assert_eq!(resolved.as_path(), Path::new("/managed/ca.pem")); } #[test] @@ -386,13 +308,11 @@ mod tests { fn gateway_guest_tls_rejects_plaintext_gateway() { let gateway = config_file::GatewayFileSection { guest_tls_ca: Some(PathBuf::from("/tmp/ca.pem")), - guest_tls_cert: Some(PathBuf::from("/tmp/cert.pem")), - guest_tls_key: Some(PathBuf::from("/tmp/key.pem")), ..Default::default() }; let error = GuestTlsPaths::resolve(Some(&gateway), None, true) .expect_err("guest TLS and plaintext gateway conflict"); - assert!(error.contains("require gateway TLS")); + assert!(error.contains("requires gateway TLS")); } #[derive(Debug, Default, Deserialize)] @@ -417,30 +337,29 @@ socket_path = "/run/openshell/kyma.sock" driver_config_from_context::(test_context(Some(&file)), "kyma") .expect_err("local driver TLS field must be rejected"); assert!(local_error.to_string().contains(field)); - assert!(local_error.to_string().contains("[openshell.gateway]")); + let guidance = if field == "guest_tls_ca" { + "[openshell.gateway]" + } else { + "no longer supported; remove it" + }; + assert!(local_error.to_string().contains(guidance)); let remote_error = remote_driver_config_from_context(test_context(Some(&file)), "kyma") .expect_err("remote driver TLS field must be rejected"); assert!(remote_error.to_string().contains(field)); - assert!(remote_error.to_string().contains("[openshell.gateway]")); + assert!(remote_error.to_string().contains(guidance)); } } #[test] fn explicit_gateway_guest_tls_takes_precedence_over_package_bundle() { let dir = tempfile::tempdir().expect("temp dir"); - let explicit = [ - dir.path().join("explicit-ca.pem"), - dir.path().join("explicit-cert.pem"), - dir.path().join("explicit-key.pem"), - ]; + let explicit = [dir.path().join("explicit-ca.pem")]; for path in &explicit { std::fs::write(path, b"explicit").expect("write explicit TLS fixture"); } let gateway = config_file::GatewayFileSection { guest_tls_ca: Some(explicit[0].clone()), - guest_tls_cert: Some(explicit[1].clone()), - guest_tls_key: Some(explicit[2].clone()), ..Default::default() }; let package = LocalTlsPaths { @@ -454,24 +373,13 @@ socket_path = "/run/openshell/kyma.sock" let resolved = GuestTlsPaths::resolve(Some(&gateway), Some(&package), false) .expect("explicit bundle resolves") .expect("guest bundle"); - assert_eq!( - resolved.as_paths(), - ( - explicit[0].as_path(), - explicit[1].as_path(), - explicit[2].as_path() - ) - ); + assert_eq!(resolved.as_path(), explicit[0].as_path()); } #[test] - fn gateway_guest_tls_rejects_directories_for_every_bundle_member() { + fn gateway_guest_tls_rejects_ca_directory() { let dir = tempfile::tempdir().expect("temp dir"); - let files = [ - dir.path().join("ca.pem"), - dir.path().join("cert.pem"), - dir.path().join("key.pem"), - ]; + let files = [dir.path().join("ca.pem")]; for path in &files { std::fs::write(path, b"fixture").expect("write TLS fixture"); } @@ -481,8 +389,6 @@ socket_path = "/run/openshell/kyma.sock" paths[index] = dir.path().to_path_buf(); let gateway = config_file::GatewayFileSection { guest_tls_ca: Some(paths[0].clone()), - guest_tls_cert: Some(paths[1].clone()), - guest_tls_key: Some(paths[2].clone()), ..Default::default() }; let error = GuestTlsPaths::resolve(Some(&gateway), None, false) diff --git a/crates/openshell-server/src/config_file.rs b/crates/openshell-server/src/config_file.rs index de3f3df6bd..e442607d9d 100644 --- a/crates/openshell-server/src/config_file.rs +++ b/crates/openshell-server/src/config_file.rs @@ -139,10 +139,6 @@ pub struct GatewayFileSection { // ── Sandbox client TLS ─────────────────────────────────────────────── #[serde(default)] pub guest_tls_ca: Option, - #[serde(default)] - pub guest_tls_cert: Option, - #[serde(default)] - pub guest_tls_key: Option, // ── TLS toggle ─────────────────────────────────────────────────────── /// When `true`, the gateway listens on plaintext HTTP and ignores any diff --git a/crates/openshell-server/src/lib.rs b/crates/openshell-server/src/lib.rs index d6d35f6ba3..72efa01fbf 100644 --- a/crates/openshell-server/src/lib.rs +++ b/crates/openshell-server/src/lib.rs @@ -1279,8 +1279,6 @@ pub struct ComputeDriverRegistration { detect: Option bool>, factory: Arc, telemetry_category: TelemetryComputeDriver, - local_singleplayer: bool, - supports_mtls_user_auth: bool, in_process_tracing: Option, } @@ -1311,8 +1309,6 @@ impl ComputeDriverRegistration { detect, factory: Arc::new(factory), telemetry_category: TelemetryComputeDriver::custom(), - local_singleplayer: false, - supports_mtls_user_auth: true, in_process_tracing: None, }) } @@ -1338,17 +1334,10 @@ impl ComputeDriverRegistration { self } - /// Mark a backend whose local deployment should use single-player defaults. + /// Compatibility no-op retained for existing factory registrations. + /// Gateway mTLS user authentication is independent of compute drivers. #[must_use] - pub fn with_local_singleplayer(mut self) -> Self { - self.local_singleplayer = true; - self - } - - /// Mark a backend that requires user authentication other than mTLS. - #[must_use] - pub fn without_mtls_user_auth(mut self) -> Self { - self.supports_mtls_user_auth = false; + pub fn with_local_singleplayer(self) -> Self { self } @@ -1362,16 +1351,6 @@ impl ComputeDriverRegistration { self } - #[must_use] - pub(crate) fn is_local_singleplayer(&self) -> bool { - self.local_singleplayer - } - - #[must_use] - pub(crate) fn supports_mtls_user_auth(&self) -> bool { - self.supports_mtls_user_auth - } - #[must_use] pub fn in_process_tracing(&self) -> Option { self.in_process_tracing @@ -1604,13 +1583,13 @@ impl ComputeDriverBuildContext<'_> { self.config.gateway_tls_enabled() } - /// Gateway client credentials that a local driver may mount into guests. + /// Gateway CA certificate that a local driver may provide to supervisors. #[must_use] - pub fn guest_tls_paths(&self) -> Option<(&Path, &Path, &Path)> { + pub fn guest_tls_ca(&self) -> Option<&Path> { self.config .driver_startup .guest_tls - .map(compute::driver_config::GuestTlsPaths::as_paths) + .map(compute::driver_config::GuestTlsPaths::as_path) } /// Deserialize the selected driver's merged TOML table. diff --git a/crates/openshell-server/src/multiplex.rs b/crates/openshell-server/src/multiplex.rs index 7f175a89ad..972df8b965 100644 --- a/crates/openshell-server/src/multiplex.rs +++ b/crates/openshell-server/src/multiplex.rs @@ -692,7 +692,6 @@ fn gateway_principal_fields(principal: &Principal) -> BTreeMap { "source".to_string(), match &sandbox.source { SandboxIdentitySource::BootstrapJwt { .. } => "bootstrap_jwt", - SandboxIdentitySource::BootstrapCert { .. } => "bootstrap_cert", SandboxIdentitySource::ComputeDriver { .. } => "compute_driver", } .to_string(), @@ -892,8 +891,9 @@ where /// Once sandbox authentication is configured, callers must present an /// explicit credential for authenticated gRPC methods. Missing bearer auth /// is promoted to an mTLS user only when `mtls_auth.enabled` is configured -/// for local single-user gateways, or to an unsafe local developer user when -/// `auth.allow_unauthenticated_users` is explicitly enabled. +/// and the connection presents a verified client certificate, or to an unsafe +/// local developer user when `auth.allow_unauthenticated_users` is explicitly +/// enabled. /// /// When neither OIDC nor sandbox credentials are configured (a barebones /// dev gateway), the chain is left as `None` so the router short-circuits diff --git a/deploy/rpm/CONFIGURATION.md b/deploy/rpm/CONFIGURATION.md index 2030bbac70..843430a63a 100644 --- a/deploy/rpm/CONFIGURATION.md +++ b/deploy/rpm/CONFIGURATION.md @@ -66,8 +66,8 @@ systemctl --user edit openshell-gateway ## TLS (mTLS) -The RPM enables mutual TLS by default. The gateway requires a valid -client certificate for all API connections. Its primary listener uses +The RPM enables mTLS user authentication by default. CLI clients present a valid +client certificate; supervisors use the gateway CA and sandbox-scoped bearer tokens. Its primary listener uses `127.0.0.1:17670`; Podman supervisor sessions use that same listener. ### Auto-generated certificates @@ -176,25 +176,20 @@ To disable TLS (not recommended for production): ## Sandbox TLS -When mTLS is enabled, the Podman driver bind-mounts the client -certificates into each sandbox container so the supervisor process can -establish an mTLS connection back to the gateway. +When TLS is enabled, the Podman driver bind-mounts the gateway CA into each +supervisor container to authenticate the gateway. Supervisors authenticate their +RPCs with sandbox-scoped bearer tokens. The user client certificate and private +key are not mounted into supervisor or workload containers. -The following TOML fields control the host-side paths of the client -certificates that are mounted into sandbox containers: +The following TOML field controls the host-side CA path: ```toml [openshell.gateway] guest_tls_ca = "/home/user/.local/state/openshell/tls/ca.crt" -guest_tls_cert = "/home/user/.local/state/openshell/tls/client/tls.crt" -guest_tls_key = "/home/user/.local/state/openshell/tls/client/tls.key" ``` -Inside the container, the supervisor reads them from: - -- `/etc/openshell/tls/client/ca.crt` -- `/etc/openshell/tls/client/tls.crt` -- `/etc/openshell/tls/client/tls.key` +Inside the supervisor container, the CA is mounted at +`/etc/openshell/tls/client/ca.crt`. On SELinux-enabled systems, the Podman driver automatically applies the `:z` relabel option to these bind mounts. No manual SELinux @@ -223,7 +218,7 @@ overrides that persist across package upgrades. | `[openshell.drivers.podman].default_image` | `nvcr.io/nvidia/base/ubuntu:24.04` | Default sandbox image. | | `[openshell.drivers.podman].sandbox_runtime_image` | `ghcr.io/nvidia/openshell/sandbox:latest` | Static musl sandbox runtime image mounted into Podman workloads. | | `[openshell.drivers.podman].supervisor_image` | `ghcr.io/nvidia/openshell/supervisor:latest` | Dynamic glibc supervisor image used outside the workload. | -| `[openshell.gateway].guest_tls_ca`, `guest_tls_cert`, `guest_tls_key` | auto-generated paths | Gateway-owned client TLS material injected into the selected local driver and mounted into sandbox containers. | +| `[openshell.gateway].guest_tls_ca` | auto-generated path | Gateway CA injected into the selected local driver for supervisor-to-gateway TLS. Sandbox identity uses a bearer token. | | `[openshell.gateway.tls]` paths | auto-generated paths | Server TLS certificate, key, and client CA. | | `disable_tls` | unset | Set to `true` to disable TLS. | diff --git a/deploy/rpm/QUICKSTART.md b/deploy/rpm/QUICKSTART.md index 107f0a4421..fe0851ebcb 100644 --- a/deploy/rpm/QUICKSTART.md +++ b/deploy/rpm/QUICKSTART.md @@ -67,9 +67,8 @@ On first start, the gateway automatically generates: > **Note:** The primary gateway listener uses the loopback default, > `127.0.0.1:17670`. Host-networked Podman supervisors use this same listener. -> Mutual TLS (mTLS) is -> enabled automatically on first start, requiring a valid client certificate -> for every connection. See CONFIGURATION.md for details. +> mTLS user authentication is enabled automatically on first start. +> Supervisor connections use the gateway CA and sandbox-scoped bearer tokens. See CONFIGURATION.md for details. Verify the service is running: diff --git a/deploy/rpm/TROUBLESHOOTING.md b/deploy/rpm/TROUBLESHOOTING.md index a975ab1e92..acb878f543 100644 --- a/deploy/rpm/TROUBLESHOOTING.md +++ b/deploy/rpm/TROUBLESHOOTING.md @@ -244,14 +244,14 @@ podman pull nvcr.io/nvidia/base/ubuntu:24.04 ### Migrating a TLS-enabled local driver to schema version 2 -Docker, Podman, and VM sandboxes connect back to the gateway with a guest TLS -bundle. Package-managed installs use the complete bundle generated under -`~/.local/state/openshell/tls`, so the RPM default requires no additional TOML. -If you override the listener with custom `--tls-cert` and `--tls-key` inputs and -do not use that managed bundle, configure all three `guest_tls_ca`, -`guest_tls_cert`, and `guest_tls_key` paths under `[openshell.gateway]`. The -gateway now fails at startup instead of allowing sandboxes to fail later. Omit -all three fields when TLS is disabled. +Docker, Podman, and VM supervisors authenticate the gateway with its CA and +authenticate RPCs with sandbox bearer tokens. Package-managed installs use the +CA generated under `~/.local/state/openshell/tls`, so the RPM default requires +no additional TOML. If you override the listener with custom `--tls-cert` and +`--tls-key` inputs and do not use that managed CA, configure `guest_tls_ca` under +`[openshell.gateway]`. Remove the retired `guest_tls_cert` and `guest_tls_key` +fields. The gateway fails at startup if the required CA is missing. Omit +`guest_tls_ca` when TLS is disabled. ### Migrating from gateway.env diff --git a/docs/about/architecture.mdx b/docs/about/architecture.mdx index 4332a1fc5f..5862988396 100644 --- a/docs/about/architecture.mdx +++ b/docs/about/architecture.mdx @@ -126,6 +126,10 @@ that signs credentials, and every credential names exactly one sandbox. | **Supervisor** to **sandbox** | The **supervisor** dials into the workload over the driver's private channel. | Mutual TLS, plus a sandbox JWT. | | Agent to **supervisor** | The agent never connects directly. The **sandbox** relays its traffic over the connection above. | Covered by the supervisor-to-sandbox channel. | +The **supervisor** verifies the gateway's TLS certificate with its CA. User +client certificates and private keys stay outside both the **supervisor** and +workload; the gateway JWT authenticates supervisor RPCs. + ### Getting the first credential The **supervisor** needs a starting credential to prove which sandbox it belongs diff --git a/docs/how-it-works/gateways/authentication.mdx b/docs/how-it-works/gateways/authentication.mdx index 3dbf6e524f..957b663fc1 100644 --- a/docs/how-it-works/gateways/authentication.mdx +++ b/docs/how-it-works/gateways/authentication.mdx @@ -33,9 +33,9 @@ The CLI uses one of these authentication modes depending on the gateway's config ### mTLS -The default mode for local Docker, Podman, and VM gateways without OIDC. The CLI presents a client certificate during the TLS handshake, and the gateway can map the verified certificate subject to a local user principal when mTLS user authentication is enabled. +The default mode for gateways with a client CA and no OIDC issuer. The CLI presents a client certificate during the TLS handshake, and the gateway can map the verified certificate subject to a local user principal when mTLS user authentication is enabled. -mTLS user authentication is for local single-user gateways. Kubernetes deployments must use OIDC or a trusted access proxy for user authentication; the Helm chart does not render `mtls_auth`. +mTLS user authentication is gateway policy and is independent of the compute driver. Shared deployments can prefer OIDC or a trusted access proxy for user identity and lifecycle management. Set these environment variables before starting the gateway: @@ -44,13 +44,13 @@ Set these environment variables before starting the gateway: | `OPENSHELL_TLS_CERT` | Path to the gateway server certificate. | | `OPENSHELL_TLS_KEY` | Path to the gateway server private key. | | `OPENSHELL_TLS_CLIENT_CA` | Path to the CA certificate that verifies CLI client certificates. | -| `OPENSHELL_ENABLE_MTLS_AUTH` | Set to `true` to authenticate CLI callers from verified client certificates. Defaults on for local Docker, Podman, and VM gateways with no OIDC issuer. | +| `OPENSHELL_ENABLE_MTLS_AUTH` | Set to `true` to authenticate CLI callers from verified client certificates. Defaults on when a client CA is configured and no OIDC issuer is present. | For local access, the server certificate must be valid for the endpoint the CLI uses. Include `localhost`, `127.0.0.1`, and `::1` in the certificate SANs when users connect to a local gateway through loopback. Package-managed local gateways generate this bundle automatically for the `openshell` gateway name. Homebrew registers `https://localhost:17670`; Debian and RPM use `https://127.0.0.1:17670`. When you register a package-managed local gateway with `openshell gateway add --local --name openshell`, the CLI refreshes its mTLS bundle from the package-managed TLS directory. -On Homebrew, the gateway service also mirrors the Docker sandbox client bundle into `$HOME/.local/state/openshell/homebrew/tls` before startup so Docker Desktop can bind-mount the files into sandbox containers. +On Homebrew, the gateway service also mirrors the gateway CA into `$HOME/.local/state/openshell/homebrew/tls` before startup so Docker Desktop can bind-mount gateway trust into supervisor containers. The driver does not mount the user client certificate or private key. The CLI loads its mTLS bundle from `~/.config/openshell/gateways//mtls/`: @@ -73,7 +73,7 @@ The connection flow: Gateways can validate OpenID Connect access tokens on gRPC requests. Configure OIDC when you want users, operators, or automation to authenticate with an identity provider such as Keycloak, Entra ID, or Okta. -OIDC is application-layer authentication. TLS still controls the transport. If TLS client certificates remain required, the CLI must also have an mTLS bundle for the gateway. +OIDC is application-layer authentication. TLS authenticates the gateway and protects the transport; OIDC clients do not need a TLS client certificate. The gateway validates any client certificate they present against the configured client CA. Configure the gateway with an issuer and audience: @@ -223,7 +223,7 @@ Common identity providers such as Keycloak (RS256), Microsoft Entra ID (RSA), an If `OPENSHELL_OIDC_SCOPES_CLAIM` is set, the gateway also enforces scopes. It accepts space-delimited scope strings such as `scope: "openid sandbox:read"` and JSON arrays such as `scp: ["sandbox:read"]`. Standard OIDC scopes such as `openid`, `profile`, `email`, and `offline_access` are ignored for authorization. `openshell:all` grants access to all scoped methods. -Supervisor-to-gateway RPCs do not use user OIDC tokens or mTLS user identity. Each sandbox supervisor presents a gateway-minted `Authorization: Bearer` token scoped to its sandbox ID. On Kubernetes, the Kubernetes compute driver validates the projected ServiceAccount token with TokenReview, verifies the live pod UID and controlling `Sandbox` ownerReference, and returns the authenticated sandbox ID plus a stable runtime identity. The gateway requires that runtime identity to match the value recorded when it provisioned the sandbox before minting a JWT. Log upload, policy status, provider environment lookup, and sandbox config sync run with sandbox-restricted scope, while CLI users authenticate with OIDC, edge auth, local mTLS user authentication, or an explicitly enabled unauthenticated local developer mode. Provider environment responses expose only the credentials and configuration attached to that sandbox, subject to endpoint binding and credential expiry checks. +Supervisor-to-gateway RPCs do not use user OIDC tokens or mTLS user identity. TLS authenticates the gateway using the configured CA; user client certificates and private keys are not mounted into supervisor or workload containers. Each sandbox supervisor presents a gateway-minted `Authorization: Bearer` token scoped to its sandbox ID. On Kubernetes, the Kubernetes compute driver validates the projected ServiceAccount token with TokenReview, verifies the live pod UID and controlling `Sandbox` ownerReference, and returns the authenticated sandbox ID plus a stable runtime identity. The gateway requires that runtime identity to match the value recorded when it provisioned the sandbox before minting a JWT. Log upload, policy status, provider environment lookup, and sandbox config sync run with sandbox-restricted scope, while CLI users authenticate with OIDC, edge auth, local mTLS user authentication, or an explicitly enabled unauthenticated local developer mode. Provider environment responses expose only the credentials and configuration attached to that sandbox, subject to endpoint binding and credential expiry checks. Re-authenticate an OIDC gateway with: diff --git a/docs/how-it-works/gateways/configuration.mdx b/docs/how-it-works/gateways/configuration.mdx index 3b3c3aaaf0..3b70608661 100644 --- a/docs/how-it-works/gateways/configuration.mdx +++ b/docs/how-it-works/gateways/configuration.mdx @@ -81,11 +81,11 @@ future version. To migrate an existing file: removed `--driver` and `--drivers` flags remain unsupported. 3. Move every compute-driver option into `[openshell.drivers.]`. Schema version 2 does not inherit driver defaults from `[openshell.gateway]`. - Keep only `guest_tls_ca`, `guest_tls_cert`, and `guest_tls_key` at gateway - scope. A TLS-enabled Docker, Podman, or VM gateway requires one complete - guest bundle. Set all three paths unless the package-managed local TLS - bundle supplies them. When TLS is disabled, omit all three. Kubernetes - projects sandbox TLS through `client_tls_secret_name` instead. + Keep `guest_tls_ca` at gateway scope and remove `guest_tls_cert` and + `guest_tls_key`. A TLS-enabled Docker, Podman, or VM gateway requires the + gateway CA unless package-managed local TLS supplies it. When TLS is + disabled, omit it. Kubernetes projects the gateway CA into supervisor Pods + through `client_tls_secret_name` instead. 4. Rename Docker `sandbox_namespace` to `sandbox_label`, Podman `sandbox_ssh_socket_path` to `ssh_socket_path`, and VM `openshell_endpoint` to `grpc_endpoint`. @@ -155,14 +155,11 @@ enable_websocket_tunnel = false # Set true only for local plaintext gateways or trusted TLS termination. disable_tls = false -# Guest TLS paths remain gateway settings. TLS-enabled Docker, Podman, and VM -# gateways require a complete bundle unless package-managed local TLS supplies -# it automatically. Omit all three when TLS is disabled. Kubernetes projects -# sandbox TLS from client_tls_secret_name instead. Driver tables must not repeat -# these fields. +# The supervisor gateway CA remains a gateway setting. TLS-enabled Docker, +# Podman, and VM gateways require it unless package-managed local TLS supplies +# it automatically. Omit it when TLS is disabled. Kubernetes projects the CA +# from client_tls_secret_name instead. Driver tables must not repeat this field. guest_tls_ca = "/etc/openshell/certs/ca.pem" -guest_tls_cert = "/etc/openshell/certs/client.pem" -guest_tls_key = "/etc/openshell/certs/client-key.pem" # Optional gRPC rate limit. Both values must be positive to enable the limit. # Set either value to 0, or omit both, to disable rate limiting. @@ -185,7 +182,7 @@ audience = "urn:openshell:middleware:local-content-guard" max_payload_bytes = 262144 timeout = "500ms" -# Gateway listener TLS (distinct from the per-driver guest_tls_*). +# Gateway listener TLS (distinct from the supervisor gateway CA). # client_ca_path is optional; omit it for HTTPS-only listeners that do not # verify client certificates. [openshell.gateway.tls] @@ -264,9 +261,9 @@ sa_token_ttl_secs = 3600 namespace = "openshell" ``` -Local Docker, Podman, and VM gateways can also set `[openshell.gateway.mtls_auth] enabled = true` to map a verified client certificate to a CLI user identity. This application-layer identity switch does not control the TLS handshake. When `client_ca_path` is set without OIDC, the listener requires a valid client certificate. When OIDC is configured, bearer-only clients may connect; the listener still validates any client certificate they present against the configured CA. Kubernetes deployments must leave `mtls_auth.enabled` unset and use OIDC or a trusted access proxy; the Helm chart does not render this table. +Set `[openshell.gateway.mtls_auth] enabled = true` to map a verified client certificate to a CLI user identity. This gateway policy is independent of the compute driver and defaults on when a client CA is configured without OIDC. The listener allows bearer-only clients and validates any client certificate presented against the configured CA. Supervisors use `guest_tls_ca` to authenticate the gateway and sandbox bearer tokens to authenticate their RPCs. -The client-certificate handshake policy is derived and has no `require_client_auth` TOML field. This preserves bearer-only OIDC clients and prevents a file setting from silently weakening CA-only gateways. +The client-certificate handshake policy has no `require_client_auth` TOML field. Client certificates are optional at the transport layer; gateway RPC authorization enforces the configured user or sandbox identity. `[openshell.gateway.tls]` supports optional SNI-based dual-certificate mode for deployments that need separate internal and external server certificates. Set `external_cert_path` and `external_key_path` to point at the external (e.g. ACME/publicly-trusted) certificate and key. List the hostnames that should be served with the external certificate in `external_server_names`. Connections whose TLS SNI hostname matches one of those names receive the external certificate; all other connections (including those with no SNI) receive the primary internal certificate from `cert_path`/`key_path`. Both fields must be set together — providing only one is a configuration error. On Kubernetes with the Helm chart, the external certificate is managed automatically when `certManager.serverIssuerRef.name` is set; the chart populates these fields from the cert-manager-issued external server certificate. @@ -703,7 +700,7 @@ Kubernetes configurations set `namespace`, `service_account_name`, and `enable_u ### Kubernetes -The gateway runs as a Pod and creates sandbox Pods in another namespace. mTLS material for sandboxes is delivered through a Kubernetes Secret rather than host-side file paths. +The gateway runs as a Pod and creates paired workload and supervisor Pods in another namespace. The gateway CA is projected from a Kubernetes Secret into supervisor Pods; user client certificate and key entries in that Secret are not exposed to either Pod. ```toml [openshell] @@ -888,7 +885,7 @@ output. ### Docker -Sandboxes run as containers on a local bridge network. The supervisor binary is bind-mounted from the host (no in-cluster image pull required). Configure guest mTLS paths once under `[openshell.gateway]`; the gateway validates and injects the bundle into the selected local driver. +Each Docker sandbox uses a workload container with networking disabled and a host-networked supervisor container. Configure the supervisor gateway CA once under `[openshell.gateway]`; the gateway validates and injects it into the selected local driver. The supervisor authenticates RPCs with a sandbox bearer token. ```toml [openshell] @@ -898,10 +895,8 @@ version = 2 bind_address = "127.0.0.1:17670" log_level = "info" compute_driver = "docker" -# Gateway-owned bundle injected into the selected local driver. +# Gateway-owned CA injected into the selected local driver. guest_tls_ca = "/etc/openshell/certs/ca.pem" -guest_tls_cert = "/etc/openshell/certs/client.pem" -guest_tls_key = "/etc/openshell/certs/client-key.pem" [openshell.drivers.docker] socket_path = "/var/run/docker.sock" @@ -974,7 +969,7 @@ path is never mounted into or exposed to the workload container. ### Podman -Each Podman sandbox uses two containers. The workload container runs `openshell-sandbox` with `network=none`; the supervisor container runs on the host network and initiates policy-approved upstream connections. A private volume carries their authenticated Unix-domain socket. Configure guest mTLS paths once under `[openshell.gateway]`; the gateway validates and injects the bundle into the selected local driver. +Each Podman sandbox uses two containers. The workload container runs `openshell-sandbox` with `network=none`; the supervisor container runs on the host network and initiates policy-approved upstream connections. A private volume carries their authenticated Unix-domain socket. Configure the supervisor gateway CA once under `[openshell.gateway]`; the gateway validates and injects it into the selected local driver. The supervisor authenticates RPCs with a sandbox bearer token. ```toml [openshell] @@ -984,10 +979,8 @@ version = 2 bind_address = "127.0.0.1:17670" log_level = "info" compute_driver = "podman" -# Gateway-owned bundle injected into the selected local driver. +# Gateway-owned CA injected into the selected local driver. guest_tls_ca = "/etc/openshell/certs/ca.pem" -guest_tls_cert = "/etc/openshell/certs/client.pem" -guest_tls_key = "/etc/openshell/certs/client-key.pem" [openshell.drivers.podman] network_name = "openshell" @@ -1139,10 +1132,8 @@ bind_address = "127.0.0.1:17670" log_level = "info" # VM is never auto-detected; an explicit entry here is required. compute_driver = "vm" -# Gateway-owned bundle injected into the selected local driver. +# Gateway-owned CA injected into the selected local driver. guest_tls_ca = "/var/lib/openshell/guest-tls/ca.pem" -guest_tls_cert = "/var/lib/openshell/guest-tls/client.pem" -guest_tls_key = "/var/lib/openshell/guest-tls/client-key.pem" [openshell.drivers.vm] state_dir = "/var/lib/openshell/vm" diff --git a/docs/how-it-works/sandboxes/runtimes.mdx b/docs/how-it-works/sandboxes/runtimes.mdx index f52a571bd2..505f2cc69b 100644 --- a/docs/how-it-works/sandboxes/runtimes.mdx +++ b/docs/how-it-works/sandboxes/runtimes.mdx @@ -36,6 +36,13 @@ When `compute_driver` is unset, the gateway auto-detects Kubernetes, then Podman Configure driver-specific values, such as images, endpoints, and sizing, under `[openshell.drivers.]`. See the [Gateway Configuration File](/how-it-works/gateways/configuration) reference for every option. +For TLS-enabled Docker, Podman, and MicroVM gateways, set `guest_tls_ca` in +`[openshell.gateway]` or use the package-managed local CA. Remove the retired +`guest_tls_cert` and `guest_tls_key` fields. Supervisors receive only the +CA and authenticate gateway RPCs with sandbox bearer tokens. Kubernetes +projects or stages only the gateway CA from its configured TLS Secret; user +client certificates and private keys stay outside the sandbox boundary. + ### Extension Drivers Any name other than a built-in driver selects an extension driver. Point the gateway at the Unix socket where the driver listens: @@ -221,7 +228,7 @@ Only the OpenShell gateway and the Agent Sandbox controller should be able to ma | `image_pull_policy` | `sandbox.image.pullPolicy` | `always`, `if_not_present`, or `never`. | | `image_pull_secrets` | `server.sandboxImagePullSecrets` | Image-pull Secrets for sandbox pods. | | `grpc_endpoint` | `server.grpcEndpoint` | Gateway endpoint reachable from sandbox pods. | -| `client_tls_secret_name` | `server.tls.clientTlsSecretName` | Secret with sandbox client TLS material. | +| `client_tls_secret_name` | `server.tls.clientTlsSecretName` | Project or stage only `ca.crt` into supervisor Pods to authenticate the gateway. | | `sandbox_runtime_image` | `sandboxRuntime.image.*` | Override the sandbox runtime image. | | `supervisor_image` | `supervisor.image.*` | Override the supervisor image. | | `workspace_default_storage_size` | `server.workspaceDefaultStorageSize` | Default workspace PVC size. | diff --git a/docs/kubernetes/access-control.mdx b/docs/kubernetes/access-control.mdx index 5b295e3211..87e6be3897 100644 --- a/docs/kubernetes/access-control.mdx +++ b/docs/kubernetes/access-control.mdx @@ -8,14 +8,15 @@ keywords: "Generative AI, Cybersecurity, Kubernetes, Authentication, mTLS, OIDC, position: 6 --- -The OpenShell gateway supports two access-control models for human callers on Kubernetes: +The OpenShell gateway supports these access-control models for human callers on Kubernetes: | Model | When to use | |---|---| +| mTLS | Gateways with a configured client CA and no OIDC issuer. The gateway maps a verified client certificate to a user identity. | | OIDC (recommended) | Production deployments. Integrates with an existing identity provider, supports role-based access control, and gives each user their own identity without distributing certificates. | | Reverse-proxy auth termination | An access proxy (Cloudflare Access, ngrok, corporate SSO) authenticates callers in front of the gateway. The gateway trusts the proxy and skips its own client-cert check. | -The Helm chart always generates mTLS certificates at install time. The gateway uses them for transport-layer security regardless of which access-control model you choose. The client bundle in the `openshell-client-tls` secret is used internally by sandbox supervisors, not for granting access to individual users. +The Helm chart generates a gateway TLS certificate and a user client certificate at install time. Supervisor Pods project only `ca.crt` from the client Secret so they can authenticate the gateway; the client certificate and private key are not exposed to sandboxes. Supervisors authenticate their RPCs with gateway-minted sandbox JWTs. For how the CLI resolves gateways and stores credentials, refer to [Gateway Authentication](/how-it-works/gateways/authentication). @@ -43,7 +44,7 @@ helm upgrade openshell \ --set server.tls.clientCaSecretName="" ``` -Set `server.tls.clientCaSecretName=""` when the gateway terminates TLS directly and browsers or CLI clients connect without client certificates. The chart omits `client_ca_path` from `gateway.toml` and does not mount the client-CA volume, leaving HTTPS-only transport with OIDC for user authentication. Do not set the value to `null`; omit the key to use the chart default, or set it to `""` to disable client certificate verification. +Client certificates are optional at the TLS handshake, so OIDC callers can connect without them. Set `server.tls.clientCaSecretName=""` to disable client-certificate verification entirely. The chart omits `client_ca_path` from `gateway.toml` and does not mount the client-CA volume, leaving HTTPS-only transport with OIDC for user authentication. Do not set the value to `null`; omit the key to use the chart default, or set it to `""` to disable client certificate verification. The `audience` value must match the client ID configured in your identity provider for the OpenShell resource server. @@ -112,7 +113,7 @@ helm upgrade openshell \ The gateway still serves TLS and sandbox supervisors still authenticate with gateway-minted sandbox JWTs. User-facing CLI/API calls without OIDC or mTLS credentials are accepted as an unauthenticated local developer principal. The proxy is responsible for authenticating callers and forwarding only authorized traffic. -When the gateway terminates TLS directly and callers connect without client certificates, also set `server.tls.clientCaSecretName=""` as described in the OIDC section above. +To disable client-certificate verification entirely, set `server.tls.clientCaSecretName=""` as described in the OIDC section above. To also disable TLS entirely (when the proxy terminates TLS before the request reaches the gateway): diff --git a/docs/kubernetes/managing-certificates.mdx b/docs/kubernetes/managing-certificates.mdx index 322815cbf1..ee011df57b 100644 --- a/docs/kubernetes/managing-certificates.mdx +++ b/docs/kubernetes/managing-certificates.mdx @@ -8,7 +8,7 @@ keywords: "Generative AI, Cybersecurity, Kubernetes, cert-manager, PKI, TLS, mTL position: 4 --- -The OpenShell gateway uses mTLS certificates for transport between the gateway and sandbox supervisors. These certificates are not Kubernetes user authentication; configure OIDC or a trusted access proxy for user access. The Helm chart supports two ways to provision and manage the certificate bundle: +The OpenShell gateway uses TLS for transport to sandbox supervisors. Supervisor Pods receive the gateway CA, not a client certificate or private key, and authenticate RPCs with sandbox JWTs. The generated client certificate can authenticate user clients when gateway mTLS user authentication is enabled; shared deployments can instead configure OIDC or a trusted access proxy. The Helm chart supports two ways to provision and manage the certificate bundle: | Mode | When to use | |---|---| diff --git a/docs/security/best-practices.mdx b/docs/security/best-practices.mdx index a310900b98..2f5b346708 100644 --- a/docs/security/best-practices.mdx +++ b/docs/security/best-practices.mdx @@ -260,14 +260,14 @@ The gateway secures communication between the CLI, sandbox workloads, and extern ### mTLS -Gateway transport uses TLS, with client certificate checks available where the deployment provides a client CA. Local single-user Docker, Podman, and VM gateways can use the verified client certificate as user authentication. Kubernetes deployments use the certificate bundle for transport and sandbox supervisor connectivity only; configure OIDC or a trusted access proxy for user authentication. +Gateway transport uses TLS, with client certificate checks available where the deployment provides a client CA. mTLS user authentication is gateway policy and does not depend on the compute driver. Sandbox supervisors receive only the gateway CA and authenticate API calls with gateway-minted sandbox JWTs. | Aspect | Detail | |---|---| -| Default | Local TLS bundles enable mTLS user authentication for single-user local gateways. Helm deployments generate mTLS certificates for transport, while sandbox supervisors authenticate API calls with gateway-minted sandbox JWTs. TLS-enabled loopback gateways also accept plaintext HTTP for sandbox service hostnames by default. | -| What you can change | Configure OIDC or a trusted access proxy for multi-user gateways, set `OPENSHELL_ENABLE_MTLS_AUTH=true` for local single-user gateways, enable `server.auth.allowUnauthenticatedUsers=true` only for trusted local Kubernetes development or a fully trusted proxy, disable TLS only for trusted reverse-proxy setups, or disable loopback service HTTP with `--enable-loopback-service-http=false`. | -| Risk if relaxed | Disabling TLS removes transport-level protection entirely. Allowing unauthenticated users removes the gateway user-auth boundary and must not be exposed to shared or public networks. Treating transport certificates as shared user identity in Kubernetes would collapse user and sandbox trust boundaries. Loopback service HTTP is local-only and rejects cross-origin browser requests, but any local process can still reach exposed service URLs directly. | -| Recommendation | Use local mTLS user authentication only for single-user Docker, Podman, and VM gateways. Use OIDC or a trusted access proxy for Kubernetes and shared deployments. | +| Default | A configured client CA without OIDC enables mTLS user authentication. Sandbox supervisors use CA-only TLS plus gateway-minted sandbox JWTs. TLS-enabled loopback gateways also accept plaintext HTTP for sandbox service hostnames by default. | +| What you can change | Configure mTLS user authentication, OIDC, or a trusted access proxy at the gateway; enable `server.auth.allowUnauthenticatedUsers=true` only for trusted local Kubernetes development or a fully trusted proxy; disable TLS only for trusted reverse-proxy setups; or disable loopback service HTTP with `--enable-loopback-service-http=false`. | +| Risk if relaxed | Disabling TLS removes transport-level protection entirely. Allowing unauthenticated users removes the gateway user-auth boundary and must not be exposed to shared or public networks. Mounting a user client certificate into a sandbox would collapse user and sandbox trust boundaries. Loopback service HTTP is local-only and rejects cross-origin browser requests, but any local process can still reach exposed service URLs directly. | +| Recommendation | Keep sandbox identity separate from user identity: expose only the gateway CA to sandboxes and require sandbox JWTs. Use managed OIDC or a trusted access proxy when certificate distribution is unsuitable for shared users. | ### SSH Tunnel Authentication diff --git a/e2e/configs/gateway/schema-v2-capability-parity.toml b/e2e/configs/gateway/schema-v2-capability-parity.toml index 3927f81df1..fd64b60b68 100644 --- a/e2e/configs/gateway/schema-v2-capability-parity.toml +++ b/e2e/configs/gateway/schema-v2-capability-parity.toml @@ -94,7 +94,7 @@ id = "gateway-listener-tls-and-sni" topics = ["auth_tls_jwt", "listeners"] origin_main_access_paths = ["--tls-cert / OPENSHELL_TLS_CERT", "--tls-key / OPENSHELL_TLS_KEY", "--tls-client-ca / OPENSHELL_TLS_CLIENT_CA", "[openshell.gateway.tls]"] schema_v2_access_paths = ["[openshell.gateway.tls].{cert_path,key_path,client_ca_path,external_cert_path,external_key_path,external_server_names}", "same CLI and environment variables for primary bundle"] -behavioral_oracle = "The listener presents the primary or configured SNI certificate, derives client-certificate requirements from client CA and OIDC presence, rejects the unsupported require_client_auth file field, and rejects incomplete TLS bundles." +behavioral_oracle = "The listener presents the primary or configured SNI certificate, rejects the unsupported require_client_auth file field, and rejects incomplete server TLS bundles. The frozen baseline derives client-certificate requirements from client CA and OIDC presence; the candidate permits bearer-only connections and validates any presented client certificate against the configured CA." required_environment = "test CA, primary and external certificates, TLS client" test_lane = "e2e-docker" status = "not_run" @@ -113,9 +113,9 @@ status = "not_run" id = "guest-callback-tls-ownership" topics = ["auth_tls_jwt", "docker", "podman", "vm"] origin_main_access_paths = ["[openshell.gateway].{guest_tls_ca,guest_tls_cert,guest_tls_key} inherited by local drivers", "driver-local guest_tls_* overrides"] -schema_v2_access_paths = ["[openshell.gateway].{guest_tls_ca,guest_tls_cert,guest_tls_key} injected only into selected Docker, Podman, or VM driver", "driver tables reject guest_tls_*"] -behavioral_oracle = "A TLS-enabled selected local driver receives one complete guest bundle; partial, misplaced, and plaintext-incompatible bundles fail closed." -required_environment = "test CA and client certificate bundle" +schema_v2_access_paths = ["[openshell.gateway].guest_tls_ca injected only into selected Docker, Podman, or VM driver", "driver tables reject guest_tls_*"] +behavioral_oracle = "A TLS-enabled selected local driver receives the gateway CA and authenticates callbacks with its sandbox bearer credential; legacy client certificate fields, misplaced TLS fields, and plaintext-incompatible CA settings fail closed." +required_environment = "test CA and sandbox bearer credential; client certificate bundle for the frozen baseline" test_lane = "e2e-docker" status = "not_run" @@ -134,7 +134,7 @@ id = "mtls-user-authentication" topics = ["auth_tls_jwt"] origin_main_access_paths = ["--enable-mtls-auth / OPENSHELL_ENABLE_MTLS_AUTH", "[openshell.gateway.mtls_auth].enabled"] schema_v2_access_paths = ["[openshell.gateway.mtls_auth].enabled", "same CLI and environment variable"] -behavioral_oracle = "A verified local client certificate maps to a user principal only when mTLS user auth is enabled and no stronger auth policy replaces it." +behavioral_oracle = "A verified client certificate maps to a user principal only when mTLS user auth is enabled and no stronger auth policy replaces it. The candidate defaults mTLS user auth on when a client CA is configured without OIDC, independently of the compute driver." required_environment = "local driver, test CA, client certificate" test_lane = "e2e-docker" status = "not_run" @@ -274,7 +274,7 @@ id = "kubernetes-core-placement-and-images" topics = ["kubernetes"] origin_main_access_paths = ["[openshell.gateway].{default_image,supervisor_image,client_tls_secret_name,service_account_name,host_gateway_ip,enable_user_namespaces,sa_token_ttl_secs} inherited by Kubernetes", "[openshell.drivers.kubernetes].{namespace,default_image,image_pull_policy,image_pull_secrets,service_account_name,supervisor_image,supervisor_image_pull_policy,grpc_endpoint,ssh_socket_path,client_tls_secret_name,host_gateway_ip,enable_user_namespaces,sa_token_ttl_secs}"] schema_v2_access_paths = ["same fields exclusively in [openshell.drivers.kubernetes]"] -behavioral_oracle = "The Kubernetes driver creates a sandbox Pod with driver-owned namespace, service account, image, pull policy, callback endpoint, SSH socket, TLS Secret, and token TTL." +behavioral_oracle = "The Kubernetes driver applies driver-owned namespace, service account, image, pull policy, callback endpoint, SSH socket, TLS Secret, and token TTL settings. The candidate projects only the gateway CA from the TLS Secret into the separate supervisor Pod and authenticates gateway RPCs with a sandbox bearer credential." required_environment = "Kubernetes cluster, namespace, service account, image pull secret, and client TLS Secret" test_lane = "e2e-kubernetes" status = "not_run" @@ -313,7 +313,7 @@ status = "not_run" id = "vm-launch-and-resource-configuration" topics = ["vm"] origin_main_access_paths = ["[openshell.gateway].{default_image,guest_tls_ca,guest_tls_cert,guest_tls_key} inherited by VM", "[openshell.drivers.vm].{grpc_endpoint,state_dir,driver_dir,default_image,bootstrap_image,krun_log_level,vcpus,mem_mib,overlay_disk_mib,sandbox_uid,sandbox_gid}", "standalone openshell-driver-vm --openshell-endpoint / OPENSHELL_GRPC_ENDPOINT"] -schema_v2_access_paths = ["[openshell.drivers.vm].{grpc_endpoint,state_dir,driver_dir,default_image,bootstrap_image,krun_log_level,vcpus,mem_mib,overlay_disk_mib,sandbox_uid,sandbox_gid}", "[openshell.gateway].{guest_tls_ca,guest_tls_cert,guest_tls_key}", "standalone openshell-driver-vm --grpc-endpoint / OPENSHELL_GRPC_ENDPOINT"] +schema_v2_access_paths = ["[openshell.drivers.vm].{grpc_endpoint,state_dir,driver_dir,default_image,bootstrap_image,krun_log_level,vcpus,mem_mib,overlay_disk_mib,sandbox_uid,sandbox_gid}", "[openshell.gateway].guest_tls_ca", "standalone openshell-driver-vm --grpc-endpoint / OPENSHELL_GRPC_ENDPOINT"] behavioral_oracle = "The gateway finds and launches the VM driver from driver_dir, forwards the TOML grpc_endpoint through the schema-appropriate standalone-driver flag, and launches a guest with the selected state, images, resources, and identity." required_environment = "Linux libkrun/KVM host, VM driver binary, and OCI image" test_lane = "e2e-vm" @@ -323,7 +323,7 @@ status = "not_run" id = "vm-guest-security-and-spiffe" topics = ["vm", "credentials"] origin_main_access_paths = ["[openshell.drivers.vm].{sandbox_uid,sandbox_gid}"] -schema_v2_access_paths = ["[openshell.drivers.vm].{sandbox_uid,sandbox_gid,https_proxy,no_proxy,proxy_auth_file,proxy_auth_allow_insecure,proxy_connect_by_hostname,provider_spiffe_workload_api_tcp_endpoint,provider_spiffe_allow_guest_tcp}", "gateway-owned guest_tls_* bundle"] +schema_v2_access_paths = ["[openshell.drivers.vm].{sandbox_uid,sandbox_gid,https_proxy,no_proxy,proxy_auth_file,proxy_auth_allow_insecure,proxy_connect_by_hostname,provider_spiffe_workload_api_tcp_endpoint,provider_spiffe_allow_guest_tcp}", "gateway-owned guest_tls_ca"] behavioral_oracle = "VM validates non-root guest ownership, callback TLS, proxy safety, and requires an explicit opt-in before exposing a guest-reachable SPIFFE TCP endpoint." required_environment = "Linux libkrun/KVM host, TLS and optional proxy/SPIFFE TCP fixture" test_lane = "e2e-vm" diff --git a/e2e/configs/gateway/schema-v2-intentional-changes.toml b/e2e/configs/gateway/schema-v2-intentional-changes.toml index d0707b8a54..78e23234b7 100644 --- a/e2e/configs/gateway/schema-v2-intentional-changes.toml +++ b/e2e/configs/gateway/schema-v2-intentional-changes.toml @@ -133,9 +133,9 @@ validation_capability_ids = ["podman-runtime-security-and-health"] id = "guest-tls-centralized" category = "ownership" origin_main_contract = "Local driver tables may override guest_tls_ca, guest_tls_cert, and guest_tls_key inherited from gateway scope." -schema_v2_contract = "One complete guest TLS bundle is gateway-owned and injected into the selected Docker, Podman, or VM driver; driver-local fields are rejected." -migration = "Keep guest_tls_ca, guest_tls_cert, and guest_tls_key under [openshell.gateway] only." -rationale = "A single active local driver needs one callback client identity, and centralized validation prevents partial or conflicting bundles." +schema_v2_contract = "The gateway CA is gateway-owned and injected into the selected Docker, Podman, or VM driver for supervisor TLS; legacy client certificate fields and driver-local guest TLS fields are rejected." +migration = "Keep guest_tls_ca under [openshell.gateway] only and remove guest_tls_cert and guest_tls_key. Supervisors authenticate gateway RPCs with sandbox bearer credentials." +rationale = "Gateway TLS trust is separate from sandbox identity. Centralized CA validation supplies supervisor trust without exposing a user client certificate or private key." parity_disposition = "intentional_change" validation_capability_ids = ["guest-callback-tls-ownership"] diff --git a/e2e/parity/test.sh b/e2e/parity/test.sh index 9d7f2e3b20..4de6824f05 100755 --- a/e2e/parity/test.sh +++ b/e2e/parity/test.sh @@ -286,6 +286,9 @@ if external: }, } ) +if external and schema == 2: + del launch["external_driver_environment"]["OPENSHELL_PODMAN_TLS_CERT"] + del launch["external_driver_environment"]["OPENSHELL_PODMAN_TLS_KEY"] Path(os.environ["OPENSHELL_PARITY_LAUNCH_MANIFEST_CAPTURE"]).write_text( json.dumps(launch, separators=(",", ":")) + "\n", encoding="utf-8" ) diff --git a/e2e/parity/verify-results.py b/e2e/parity/verify-results.py index 4b9bc1e7b0..dbe1fc9754 100644 --- a/e2e/parity/verify-results.py +++ b/e2e/parity/verify-results.py @@ -398,10 +398,12 @@ def verify_variant( "OPENSHELL_SANDBOX_RUNTIME_IMAGE", "OPENSHELL_SUPERVISOR_IMAGE", "OPENSHELL_PODMAN_TLS_CA", - "OPENSHELL_PODMAN_TLS_CERT", - "OPENSHELL_PODMAN_TLS_KEY", "OPENSHELL_ENABLE_BIND_MOUNTS", } + tls_fields = {"OPENSHELL_PODMAN_TLS_CA"} + if schema_version == 1: + tls_fields.update({"OPENSHELL_PODMAN_TLS_CERT", "OPENSHELL_PODMAN_TLS_KEY"}) + expected_environment_keys.update(tls_fields) require( isinstance(driver_environment, dict) and set(driver_environment) == expected_environment_keys, @@ -441,11 +443,7 @@ def verify_variant( f"{launch_path}: external driver allowlisted runtime inputs differ", ) tls_paths: set[str] = set() - for field in ( - "OPENSHELL_PODMAN_TLS_CA", - "OPENSHELL_PODMAN_TLS_CERT", - "OPENSHELL_PODMAN_TLS_KEY", - ): + for field in tls_fields: tls_input = driver_environment[field] require( isinstance(tls_input, dict) @@ -458,7 +456,7 @@ def verify_variant( ) tls_paths.add(tls_input["path"]) require( - len(tls_paths) == 3, + len(tls_paths) == len(tls_fields), f"{launch_path}: external driver TLS paths are not distinct", ) else: @@ -659,8 +657,6 @@ def verify_topology( ) for field in ( "OPENSHELL_PODMAN_TLS_CA", - "OPENSHELL_PODMAN_TLS_CERT", - "OPENSHELL_PODMAN_TLS_KEY", ): require( baseline_env[field]["path"] != candidate_env[field]["path"], diff --git a/e2e/python/test_security_tls.py b/e2e/python/test_security_tls.py index 529404a6e4..31e0fa4a0a 100644 --- a/e2e/python/test_security_tls.py +++ b/e2e/python/test_security_tls.py @@ -1,12 +1,11 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -"""E2e tests for server mTLS enforcement. +"""E2e tests for gateway TLS and mTLS user authentication. -These tests verify that the OpenShell server correctly requires valid client -certificates signed by the cluster CA. Only callers presenting the provisioned -mTLS client cert should be able to reach the OpenShell gRPC API; all other -connection attempts must be rejected. +TLS accepts CA-only clients so supervisors can use sandbox bearer tokens. +Health is public; user RPCs require an authenticated user. Presented client +certificates must be signed by the gateway CA. """ from __future__ import annotations @@ -21,7 +20,7 @@ import grpc import pytest -from openshell._proto import openshell_pb2, openshell_pb2_grpc +from openshell._proto import datamodel_pb2, openshell_pb2, openshell_pb2_grpc # --------------------------------------------------------------------------- # Helpers @@ -137,14 +136,14 @@ def mtls_certs( class TestServerMtlsEnforcement: - """Verify the server rejects callers without a valid client certificate.""" + """Verify TLS trust and the mTLS user authorization boundary.""" def test_authenticated_client_succeeds( self, server_endpoint: tuple[str, int, str], mtls_certs: tuple[bytes, bytes, bytes], ) -> None: - """A client presenting the correct mTLS cert can call Health.""" + """A verified mTLS user can call Health and a protected user RPC.""" host, port, _ = server_endpoint ca, cert, key = mtls_certs @@ -158,15 +157,21 @@ def test_authenticated_client_succeeds( stub = openshell_pb2_grpc.OpenShellStub(channel) response = stub.Health(openshell_pb2.HealthRequest(), timeout=10) assert response.status == openshell_pb2.SERVICE_STATUS_HEALTHY + stub.ListSandboxes( + openshell_pb2.ListSandboxesRequest( + workspace_scope=datamodel_pb2.WorkspaceSelector(workspace="default") + ), + timeout=10, + ) finally: channel.close() - def test_no_client_cert_rejected( + def test_ca_only_client_health_succeeds_but_user_rpc_rejected( self, server_endpoint: tuple[str, int, str], mtls_certs: tuple[bytes, bytes, bytes], ) -> None: - """A client that trusts the CA but presents no client cert is rejected.""" + """CA-only TLS reaches Health but cannot acquire mTLS user identity.""" host, port, _ = server_endpoint ca, _, _ = mtls_certs @@ -175,14 +180,32 @@ def test_no_client_cert_rejected( channel = grpc.secure_channel(f"{host}:{port}", credentials) try: stub = openshell_pb2_grpc.OpenShellStub(channel) + response = stub.Health(openshell_pb2.HealthRequest(), timeout=10) + assert response.status == openshell_pb2.SERVICE_STATUS_HEALTHY with pytest.raises(grpc.RpcError) as exc_info: - stub.Health(openshell_pb2.HealthRequest(), timeout=10) - # The server should terminate the TLS handshake or return - # UNAVAILABLE because the client did not present a certificate. - assert exc_info.value.code() in ( - grpc.StatusCode.UNAVAILABLE, - grpc.StatusCode.UNKNOWN, - ), f"expected UNAVAILABLE or UNKNOWN, got {exc_info.value.code()}" + stub.ListSandboxes( + openshell_pb2.ListSandboxesRequest( + workspace_scope=datamodel_pb2.WorkspaceSelector( + workspace="default" + ) + ), + timeout=10, + ) + assert exc_info.value.code() == grpc.StatusCode.UNAUTHENTICATED + + # An unverified bearer token must not promote the TLS connection + # to a user identity either. + with pytest.raises(grpc.RpcError) as exc_info: + stub.ListSandboxes( + openshell_pb2.ListSandboxesRequest( + workspace_scope=datamodel_pb2.WorkspaceSelector( + workspace="default" + ) + ), + metadata=(("authorization", "Bearer invalid-token"),), + timeout=10, + ) + assert exc_info.value.code() == grpc.StatusCode.UNAUTHENTICATED finally: channel.close() diff --git a/e2e/rust/e2e-vm.sh b/e2e/rust/e2e-vm.sh index a30f5283d3..ebb160c987 100755 --- a/e2e/rust/e2e-vm.sh +++ b/e2e/rust/e2e-vm.sh @@ -262,8 +262,6 @@ version = 2 bind_address = "127.0.0.1:${HOST_PORT}" compute_driver = "vm" guest_tls_ca = "${PKI_DIR}/ca.crt" -guest_tls_cert = "${PKI_DIR}/client/tls.crt" -guest_tls_key = "${PKI_DIR}/client/tls.key" [openshell.gateway.tls] cert_path = "${PKI_DIR}/server/tls.crt" @@ -301,8 +299,6 @@ if [ "${OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER:-0}" = "1" ]; then --default-image "${SANDBOX_IMAGE}" \ --state-dir "${RUN_STATE_DIR}" \ --guest-tls-ca "${PKI_DIR}/ca.crt" \ - --guest-tls-cert "${PKI_DIR}/client/tls.crt" \ - --guest-tls-key "${PKI_DIR}/client/tls.key" \ >"${DRIVER_LOG}" 2>&1 & DRIVER_PID=$! e2e_wait_for_socket \ diff --git a/e2e/rust/tests/service_bearer_passthrough.rs b/e2e/rust/tests/service_bearer_passthrough.rs index af2fddb76d..3da8a60eb7 100644 --- a/e2e/rust/tests/service_bearer_passthrough.rs +++ b/e2e/rust/tests/service_bearer_passthrough.rs @@ -279,7 +279,7 @@ async fn wait_for_authorization(url: &str, expected: &str) -> Result<(), String> #[tokio::test] async fn service_bearer_passthrough_preserves_authorization_header() { - let sandbox_name = format!("service-auth-{}", std::process::id()); + let sandbox_name = format!("svc-auth-{}", std::process::id()); let create = run_cli(&[ "sandbox", "create", diff --git a/e2e/support/podman-gateway-config.sh b/e2e/support/podman-gateway-config.sh index bd5a39b1f7..e09a208ed7 100755 --- a/e2e/support/podman-gateway-config.sh +++ b/e2e/support/podman-gateway-config.sh @@ -143,8 +143,6 @@ e2e_write_podman_gateway_config() { while IFS= read -r line; do if [ "${line}" = "[openshell.drivers.podman]" ]; then printf 'guest_tls_ca = %s\n' "$(e2e_podman_toml_string "${pki_dir}/ca.crt")" - printf 'guest_tls_cert = %s\n' "$(e2e_podman_toml_string "${pki_dir}/client/tls.crt")" - printf 'guest_tls_key = %s\n\n' "$(e2e_podman_toml_string "${pki_dir}/client/tls.key")" fi printf '%s\n' "${line}" done <"${output}" >"${configured_with_tls}" diff --git a/e2e/with-docker-gateway.sh b/e2e/with-docker-gateway.sh index 4b96693ab1..2298c2312c 100755 --- a/e2e/with-docker-gateway.sh +++ b/e2e/with-docker-gateway.sh @@ -605,8 +605,6 @@ GATEWAY_CONFIG="${STATE_DIR}/gateway.toml" printf '[openshell]\nversion = 2\n\n' printf '[openshell.gateway]\nlog_level = "info"\n' printf 'guest_tls_ca = %s\n' "$(toml_string "${PKI_DIR}/ca.crt")" - printf 'guest_tls_cert = %s\n' "$(toml_string "${PKI_DIR}/client/tls.crt")" - printf 'guest_tls_key = %s\n\n' "$(toml_string "${PKI_DIR}/client/tls.key")" e2e_write_gateway_jwt_config "${JWT_DIR}" "openshell-e2e-docker-${HOST_PORT}" if [ "${OIDC_MODE}" != "1" ]; then e2e_write_gateway_mtls_auth_config @@ -638,8 +636,6 @@ if [ "${OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER:-0}" = "1" ]; then printf 'default_image = %s\n' "$(toml_string "${SANDBOX_IMAGE}")" printf 'image_pull_policy = %s\n' "$(toml_string "${SANDBOX_IMAGE_PULL_POLICY}")" printf 'guest_tls_ca = %s\n' "$(toml_string "${PKI_DIR}/ca.crt")" - printf 'guest_tls_cert = %s\n' "$(toml_string "${PKI_DIR}/client/tls.crt")" - printf 'guest_tls_key = %s\n' "$(toml_string "${PKI_DIR}/client/tls.key")" printf 'enable_bind_mounts = true\n' printf 'sandbox_runtime_image = %s\n' "$(toml_string "${SANDBOX_RUNTIME_IMAGE}")" printf 'supervisor_image = %s\n' "$(toml_string "${SUPERVISOR_IMAGE}")" diff --git a/e2e/with-podman-gateway.sh b/e2e/with-podman-gateway.sh index 7350dd07e5..20e40fce9d 100755 --- a/e2e/with-podman-gateway.sh +++ b/e2e/with-podman-gateway.sh @@ -745,6 +745,14 @@ EXTERNAL_DRIVER_ENABLE_BIND_MOUNTS=true EXTERNAL_DRIVER_TLS_CA="${PKI_DIR}/ca.crt" EXTERNAL_DRIVER_TLS_CERT="${PKI_DIR}/client/tls.crt" EXTERNAL_DRIVER_TLS_KEY="${PKI_DIR}/client/tls.key" +# The frozen schema-v1 baseline still requires a gateway client identity. +external_driver_legacy_tls_env=() +if [ "${CONFIG_SCHEMA_VERSION}" = "1" ]; then + external_driver_legacy_tls_env=( + "OPENSHELL_PODMAN_TLS_CERT=${EXTERNAL_DRIVER_TLS_CERT}" + "OPENSHELL_PODMAN_TLS_KEY=${EXTERNAL_DRIVER_TLS_KEY}" + ) +fi if [ -n "${OPENSHELL_PARITY_LAUNCH_MANIFEST_CAPTURE:-}" ]; then driver_transport=in_tree external_driver_grpc_endpoint=null @@ -759,9 +767,15 @@ if [ -n "${OPENSHELL_PARITY_LAUNCH_MANIFEST_CAPTURE:-}" ]; then external_driver_grpc_endpoint="\"${EXTERNAL_DRIVER_GRPC_ENDPOINT}\"" external_driver_host_gateway_ip='"host-gateway"' driver_tls_ca_sha256="$(sha256sum "${EXTERNAL_DRIVER_TLS_CA}" | cut -d' ' -f1)" - driver_tls_cert_sha256="$(sha256sum "${EXTERNAL_DRIVER_TLS_CERT}" | cut -d' ' -f1)" - driver_tls_key_sha256="$(sha256sum "${EXTERNAL_DRIVER_TLS_KEY}" | cut -d' ' -f1)" - external_driver_environment="$(printf '{\"XDG_DATA_HOME\":\"%s\",\"OPENSHELL_COMPUTE_DRIVER_SOCKET\":\"%s\",\"OPENSHELL_PODMAN_SOCKET\":\"%s\",\"OPENSHELL_SANDBOX_IMAGE\":\"%s\",\"OPENSHELL_SANDBOX_IMAGE_PULL_POLICY\":\"%s\",\"OPENSHELL_HEALTH_CHECK_INTERVAL_SECS\":%s,\"OPENSHELL_GRPC_ENDPOINT\":\"%s\",\"OPENSHELL_GATEWAY_PORT\":%s,\"OPENSHELL_NETWORK_NAME\":\"%s\",\"OPENSHELL_STOP_TIMEOUT\":%s,\"OPENSHELL_SANDBOX_RUNTIME_IMAGE\":\"%s\",\"OPENSHELL_SUPERVISOR_IMAGE\":\"%s\",\"OPENSHELL_PODMAN_TLS_CA\":{\"path\":\"%s\",\"sha256\":\"%s\"},\"OPENSHELL_PODMAN_TLS_CERT\":{\"path\":\"%s\",\"sha256\":\"%s\"},\"OPENSHELL_PODMAN_TLS_KEY\":{\"path\":\"%s\",\"sha256\":\"%s\"},\"OPENSHELL_ENABLE_BIND_MOUNTS\":%s}' \ + legacy_tls_manifest="" + if [ "${CONFIG_SCHEMA_VERSION}" = "1" ]; then + driver_tls_cert_sha256="$(sha256sum "${EXTERNAL_DRIVER_TLS_CERT}" | cut -d' ' -f1)" + driver_tls_key_sha256="$(sha256sum "${EXTERNAL_DRIVER_TLS_KEY}" | cut -d' ' -f1)" + legacy_tls_manifest="$(printf ',"OPENSHELL_PODMAN_TLS_CERT":{"path":"%s","sha256":"%s"},"OPENSHELL_PODMAN_TLS_KEY":{"path":"%s","sha256":"%s"}' \ + "${EXTERNAL_DRIVER_TLS_CERT}" "${driver_tls_cert_sha256}" \ + "${EXTERNAL_DRIVER_TLS_KEY}" "${driver_tls_key_sha256}")" + fi + external_driver_environment="$(printf '{\"XDG_DATA_HOME\":\"%s\",\"OPENSHELL_COMPUTE_DRIVER_SOCKET\":\"%s\",\"OPENSHELL_PODMAN_SOCKET\":\"%s\",\"OPENSHELL_SANDBOX_IMAGE\":\"%s\",\"OPENSHELL_SANDBOX_IMAGE_PULL_POLICY\":\"%s\",\"OPENSHELL_HEALTH_CHECK_INTERVAL_SECS\":%s,\"OPENSHELL_GRPC_ENDPOINT\":\"%s\",\"OPENSHELL_GATEWAY_PORT\":%s,\"OPENSHELL_NETWORK_NAME\":\"%s\",\"OPENSHELL_STOP_TIMEOUT\":%s,\"OPENSHELL_SANDBOX_RUNTIME_IMAGE\":\"%s\",\"OPENSHELL_SUPERVISOR_IMAGE\":\"%s\",\"OPENSHELL_PODMAN_TLS_CA\":{\"path\":\"%s\",\"sha256\":\"%s\"}%s,\"OPENSHELL_ENABLE_BIND_MOUNTS\":%s}' \ "${DRIVER_DATA_HOME}" \ "${DRIVER_SOCKET}" \ "${OPENSHELL_PODMAN_SOCKET:-}" \ @@ -776,10 +790,7 @@ if [ -n "${OPENSHELL_PARITY_LAUNCH_MANIFEST_CAPTURE:-}" ]; then "${SUPERVISOR_RUNTIME_IMAGE}" \ "${EXTERNAL_DRIVER_TLS_CA}" \ "${driver_tls_ca_sha256}" \ - "${EXTERNAL_DRIVER_TLS_CERT}" \ - "${driver_tls_cert_sha256}" \ - "${EXTERNAL_DRIVER_TLS_KEY}" \ - "${driver_tls_key_sha256}" \ + "${legacy_tls_manifest}" \ "${EXTERNAL_DRIVER_ENABLE_BIND_MOUNTS}")" fi printf '{"schema_version":%s,"gateway_port":%s,"external_compute_driver":%s,"compute_driver_transport":"%s","external_driver_pull_policy":"%s","supervisor_image":"%s","supervisor_image_id":"%s","supervisor_image_digest":"%s","supervisor_runtime_image":"%s","supervisor_base_image":"%s","supervisor_base_image_id":"%s","supervisor_base_image_digest":"%s","supervisor_base_runtime_image":"%s","supervisor_package_manifest_sha256":"%s","sandbox_image_request":"%s","sandbox_image_id":"%s","sandbox_image_digest":"%s","sandbox_runtime_image":"%s","sandbox_boundary_image":"%s","sandbox_client_image_alias":"%s","sandbox_client_image_alias_id":"%s","gateway_sha256_before_execution":"%s","cli_sha256_before_execution":"%s","conformance_sha256_before_execution":"%s","external_driver_sha256_before_execution":"%s","supervisor_sha256_before_execution":"%s","supervisor_dockerfile_sha256_before_execution":"%s","cli_trace_wrapper_sha256_before_execution":"%s","external_driver_grpc_endpoint":%s,"external_driver_host_gateway_ip":%s,"external_driver_userns":%s,"external_driver_spiffe":%s,"external_driver_proxy":%s,"external_driver_app_armor":%s,"external_driver_environment":%s}\n' \ @@ -838,8 +849,7 @@ if [ "${OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER:-0}" = "1" ]; then OPENSHELL_SANDBOX_RUNTIME_IMAGE="${SANDBOX_BOUNDARY_IMAGE}" \ OPENSHELL_SUPERVISOR_IMAGE="${SUPERVISOR_RUNTIME_IMAGE}" \ OPENSHELL_PODMAN_TLS_CA="${EXTERNAL_DRIVER_TLS_CA}" \ - OPENSHELL_PODMAN_TLS_CERT="${EXTERNAL_DRIVER_TLS_CERT}" \ - OPENSHELL_PODMAN_TLS_KEY="${EXTERNAL_DRIVER_TLS_KEY}" \ + "${external_driver_legacy_tls_env[@]}" \ OPENSHELL_ENABLE_BIND_MOUNTS="${EXTERNAL_DRIVER_ENABLE_BIND_MOUNTS}" \ "${DRIVER_BIN}" >"${DRIVER_LOG}" 2>&1 & DRIVER_PID=$! diff --git a/python/openshell/gateway_schema_v2_compute_boundary_verifier_test.py b/python/openshell/gateway_schema_v2_compute_boundary_verifier_test.py index aace841cfb..5e93d7cbe2 100644 --- a/python/openshell/gateway_schema_v2_compute_boundary_verifier_test.py +++ b/python/openshell/gateway_schema_v2_compute_boundary_verifier_test.py @@ -107,6 +107,18 @@ def create_variant( policy = "missing" if schema_version == 1 else "if_not_present" package_hash = verifier.sha256(artifact_dir / "supervisor.packages.txt") result = json.loads((results_dir / f"{variant}.json").read_text(encoding="utf-8")) + legacy_tls_environment = {} + if schema_version == 1: + legacy_tls_environment = { + "OPENSHELL_PODMAN_TLS_CERT": { + "path": f"/tmp/{variant}-pki/tls.crt", + "sha256": "9" * 64, + }, + "OPENSHELL_PODMAN_TLS_KEY": { + "path": f"/tmp/{variant}-pki/tls.key", + "sha256": "a" * 64, + }, + } write_json( results_dir / f"{variant}.launch.json", { @@ -164,14 +176,7 @@ def create_variant( "path": f"/tmp/{variant}-pki/ca.crt", "sha256": "8" * 64, }, - "OPENSHELL_PODMAN_TLS_CERT": { - "path": f"/tmp/{variant}-pki/tls.crt", - "sha256": "9" * 64, - }, - "OPENSHELL_PODMAN_TLS_KEY": { - "path": f"/tmp/{variant}-pki/tls.key", - "sha256": "a" * 64, - }, + **legacy_tls_environment, "OPENSHELL_ENABLE_BIND_MOUNTS": True, }, }, diff --git a/rfc/0003-gateway-configuration/README.md b/rfc/0003-gateway-configuration/README.md index 4527c85d08..6e42e76652 100644 --- a/rfc/0003-gateway-configuration/README.md +++ b/rfc/0003-gateway-configuration/README.md @@ -93,10 +93,8 @@ enable_loopback_service_http = true # plaintext listener; guest TLS fields must then be omitted. disable_tls = false -# Gateway-owned TLS bundle injected into the selected local driver. +# Gateway-owned CA injected into the selected local driver for supervisor TLS. guest_tls_ca = "/etc/openshell/certs/ca.pem" -guest_tls_cert = "/etc/openshell/certs/client.pem" -guest_tls_key = "/etc/openshell/certs/client-key.pem" [openshell.gateway.tls] cert_path = "/etc/openshell/certs/gateway.pem" @@ -162,7 +160,7 @@ krun_log_level = 1 Each `[openshell.drivers.]` table is extracted from the parsed file and handed to the driver's initialization function as a raw TOML value. The driver is then responsible for: 1. **Parsing** — deserializing the table into its own typed config struct (e.g. `KubernetesComputeConfig`, `DockerComputeConfig`, `PodmanComputeConfig`, `VmComputeConfig`). -2. **Validation** — applying cross-field checks specific to that driver. Gateway-owned guest TLS paths are validated as one bundle and injected only into the selected local driver before this step. +2. **Validation** — applying cross-field checks specific to that driver. The gateway-owned CA path is validated and injected only into the selected local driver before this step; supervisor identity uses sandbox bearer tokens. 3. **Consumption** — using the resulting struct to initialize internal state. Driver authors define and own their config schema. Adding a new driver does not require changes to the gateway's core `Config` struct or to this RFC. diff --git a/skills/debug-openshell-cluster/SKILL.md b/skills/debug-openshell-cluster/SKILL.md index 6382428946..c8b763e399 100644 --- a/skills/debug-openshell-cluster/SKILL.md +++ b/skills/debug-openshell-cluster/SKILL.md @@ -71,7 +71,7 @@ Common findings: - `No active gateway`: register one with `openshell gateway add `. - Connection refused: gateway process is not running, service exposure is wrong, or a port-forward/proxy is not active. -- TLS/certificate errors: the endpoint scheme or trust chain is wrong, a local mTLS bundle does not match the gateway CA, or TLS termination does not match the gateway listener. +- TLS/certificate errors: the endpoint scheme or trust chain is wrong, a CLI mTLS bundle does not match the gateway CA, a supervisor is missing the gateway CA, or TLS termination does not match the gateway listener. Workloads and supervisors should not contain a user TLS client certificate or private key. - A Snap refresh restarts the gateway with its migrated mTLS config. The secure Snap gateway uses `https://127.0.0.1:17670` and requires a client bundle in the user's Snap state. Refresh replaces insecure configs without keeping a copy; follow the published Snap installation steps to re-register an old HTTP client. - `Unauthenticated` from an edge or OIDC gateway: refresh stored credentials with `openshell gateway login [name]`, then retry. Use `gateway logout` only when intentionally clearing local credentials. - A direct development endpoint with a private or self-signed certificate can be isolated with `--gateway-endpoint --gateway-insecure`; do not persist or recommend insecure verification for shared gateways. @@ -130,11 +130,13 @@ WebSocket tunnel for edge-proxy CLI access is off unless and RPM package startup migrates only exact package-generated v1 defaults. If an upgraded package still reports an unsupported version, inspect the active prefix or `~/.config/openshell/gateway.toml`; an edited v1 file must follow the published schema-v2 migration steps and must not be overwritten. -Guest TLS CA, certificate, and key paths are the exception to driver ownership: -configure the complete bundle under `[openshell.gateway]`, and the gateway -injects it only into the selected local driver. TLS-enabled Docker, Podman, and -VM drivers fail startup when neither those paths nor the package-managed local -bundle is available; Kubernetes projects its bundle through a Secret. +The supervisor gateway CA is the exception to driver ownership: configure +`guest_tls_ca` under `[openshell.gateway]`, and the gateway injects it only into +the selected local driver. Remove the retired `guest_tls_cert` and +`guest_tls_key` fields. TLS-enabled Docker, Podman, and VM drivers fail startup +when neither that CA nor the package-managed local CA is available. Kubernetes +projects only the gateway CA from a Secret into supervisor Pods; supervisors +authenticate gateway RPCs with sandbox bearer tokens. Custom names use `[openshell.drivers.].socket_path`. A launch-time `--compute-driver-socket` override may also use `docker`, `podman`, `kubernetes`, or `vm`; the endpoint then takes precedence over built-in construction. First-party standalone drivers require the socket parent directory to be owned by the driver's effective UID, force its mode to `0700`, create the socket with mode `0600`, and accept only peers with that same UID. Check the parent and socket separately with `stat`; a gateway running under a different UID cannot connect even when filesystem permissions or group membership would otherwise allow it. Operator-supplied drivers must provide equivalent access control appropriate to their implementation. Check gateway logs for connection errors, `GetCapabilities` failures, missing peer metadata, protocol-major mismatch, unmet required capabilities, or an unexpected advertised driver name. `openshell gateway info` reports successful startup negotiations. The advertised name is diagnostic metadata; negotiated features control optional behavior. The gateway does not create or supervise operator-supplied driver processes or sockets. @@ -574,7 +576,9 @@ Less commonly, `UnknownCA` can occur if the gateway's client-verification CA is misconfigured. The default `clientCaFromServerTlsSecret=true` is correct for all configurations — the internal server certificate is always signed by the chart CA (the same CA that signs the client cert), so its `ca.crt` is -the right trust anchor. Only override this if you intentionally mount a +the right trust anchor. Supervisor Pods project only `ca.crt` from the copied +Secret; `tls.crt` and `tls.key` are reserved for user clients and must not be +visible in a sandbox. Only override this if you intentionally mount a separate client CA via `server.tls.clientCaSecretName`. Verify the mounted client CA matches the CA that signed the client certificate: From 672924027a936ba90b3f3c209657e2d8d71e7801 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 05:19:33 +0000 Subject: [PATCH 15/33] chore(deps): update registry.access.redhat.com/ubi9/nodejs-24-minimal docker tag to v9.8-1790647840 (#67) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- deploy/docker/Dockerfile.konflux.openclaw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/docker/Dockerfile.konflux.openclaw b/deploy/docker/Dockerfile.konflux.openclaw index dc535ec095..5f992f1756 100644 --- a/deploy/docker/Dockerfile.konflux.openclaw +++ b/deploy/docker/Dockerfile.konflux.openclaw @@ -25,7 +25,7 @@ # `&& \` continuations only. # Both stages use the same base and OpenClaw version; bump them only here. -ARG NODEJS_IMAGE=registry.access.redhat.com/ubi9/nodejs-24-minimal:9.8@sha256:9785f7415bff723e0dfcb1b928d81a06bf004b6a1e8a941bd08983e61b577b46 +ARG NODEJS_IMAGE=registry.access.redhat.com/ubi9/nodejs-24-minimal:9.8-1790647840@sha256:9785f7415bff723e0dfcb1b928d81a06bf004b6a1e8a941bd08983e61b577b46 ARG OPENCLAW_VERSION=2026.9.5 # --------------------------------------------------------------------------- From e21b7fd8cfc170385c822566176dbd31e1462c0e Mon Sep 17 00:00:00 2001 From: Simon Scatton <44714756+SDAChess@users.noreply.github.com> Date: Thu, 1 Oct 2026 12:08:33 +0000 Subject: [PATCH 16/33] chore(build): remove bundled Z3 support (#3275) * chore(build): remove bundled Z3 support Signed-off-by: Simon Scatton Signed-off-by: Piotr Mlocek * fix(build): preserve vendored Z3 for local gateway artifacts Signed-off-by: Simon Scatton --------- Signed-off-by: Simon Scatton Signed-off-by: Piotr Mlocek --- .../build-openshell-mxc-windows/SKILL.md | 2 +- CONTRIBUTING.md | 25 ++-- Cargo.toml | 2 +- crates/openshell-gateway/Cargo.toml | 2 +- crates/openshell-prover-cli/Cargo.toml | 2 +- crates/openshell-prover/Cargo.toml | 2 +- crates/openshell-server/Cargo.toml | 2 +- deploy/docker/Dockerfile.cli-macos | 131 ------------------ deploy/docker/Dockerfile.driver-vm-macos | 119 ---------------- deploy/docker/Dockerfile.gateway-macos | 112 --------------- docs/observability/telemetry.mdx | 4 +- e2e/run.sh | 4 +- e2e/with-kube-gateway.sh | 5 +- tasks/ci.toml | 2 +- tasks/scripts/setup-zig-cc-wrapper.sh | 20 --- tasks/scripts/stage-prebuilt-binaries.sh | 4 +- tasks/scripts/trivy-scan.sh | 1 - 17 files changed, 27 insertions(+), 412 deletions(-) delete mode 100644 deploy/docker/Dockerfile.cli-macos delete mode 100644 deploy/docker/Dockerfile.driver-vm-macos delete mode 100644 deploy/docker/Dockerfile.gateway-macos diff --git a/.agents/skills/build-openshell-mxc-windows/SKILL.md b/.agents/skills/build-openshell-mxc-windows/SKILL.md index 56a86fcd37..8b1bcc0060 100644 --- a/.agents/skills/build-openshell-mxc-windows/SKILL.md +++ b/.agents/skills/build-openshell-mxc-windows/SKILL.md @@ -265,7 +265,7 @@ MXC on Windows. Each other `compute-driver-*` feature installs its own Windows rejection stub without linking that driver crate. The default `in-tree-compute-drivers` alias enables all five features. An MXC-only build uses `--no-default-features --features compute-driver-mxc` (add `telemetry` -and `bundled-z3` as needed). +and `openshell-server/prebuilt-z3` as needed). | Driver | Windows build behavior | Runtime behavior | |---|---|---| diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 935328b2b4..23fed5a334 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -309,17 +309,17 @@ Project requirements: - Rust 1.94+ - Python 3.11+ - Docker (running) -- CMake 3.16+ (only required when building with the `bundled-z3` feature) ### Z3 installation The `openshell-prover` crate and standalone `openshell-prover-cli` binary link directly against Z3. The `openshell-server` crate depends on the prover, and the `openshell-gateway` binary crate depends on `openshell-server` in turn. -These packages forward a `bundled-z3` feature to -`openshell-prover/bundled-z3`. The `openshell-cli` crate does not depend on Z3. -On macOS and Linux, install the system Z3 development package; `z3-sys` -discovers it through `pkg-config`. +The `openshell-cli` crate does not depend on Z3. The Nix development shell +supplies Z3. For builds outside that shell on macOS and Linux, install the +system Z3 development package; `z3-sys` discovers it through `pkg-config`. +The linker uses the installed static or shared library. The Nix development +shell provides a static Z3 library. ```bash # macOS @@ -332,14 +332,17 @@ sudo apt install libz3-dev sudo dnf install z3-devel ``` -If you prefer not to install Z3 system-wide, use the bundled Z3 feature. This -compiles Z3 from source during the Rust build and requires CMake 3.16+: +To build Z3 from source instead, enable `vendored-z3` (requires CMake and a C++ +compiler): ```bash -cargo build -p openshell-prover --features bundled-z3 -cargo build -p openshell-prover-cli --features bundled-z3 +cargo build -p openshell-prover --features vendored-z3 +cargo build -p openshell-prover-cli --features vendored-z3 ``` +Local gateway image and E2E builds enable `vendored-z3` so their +copied gateway binaries do not need a shared Z3 library in the runtime image. + For x86-64 and ARM64 Windows MSVC builds, use one of these Z3 paths: - Prebuilt Z3 (the default for `windows:*` tasks): `z3-sys` downloads the @@ -353,14 +356,12 @@ For x86-64 and ARM64 Windows MSVC builds, use one of these Z3 paths: target-compatible MSVC Z3 library and `Z3_SYS_Z3_HEADER` at the full path to `z3.h`. The `windows:*` tasks use this path automatically when `Z3_LIBRARY_PATH_OVERRIDE` is set. -- Bundled Z3: for direct Cargo builds, pass `--features bundled-z3` so `z3-sys` - builds Z3 from source. `openshell-prover` itself has no `bindgen`/`libclang` dependency, so building just this crate does not require `LIBCLANG_PATH`: ```powershell -cargo build -p openshell-prover --target x86_64-pc-windows-msvc --features bundled-z3 +cargo build -p openshell-prover --target x86_64-pc-windows-msvc --features prebuilt-z3 ``` ### Windows full build diff --git a/Cargo.toml b/Cargo.toml index 7cab66d291..a50457f136 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -143,7 +143,7 @@ k8s-openapi = { version = "0.24", features = ["v1_29"] } uuid = { version = "1.10", features = ["v4"] } signal-hook = "0.3" -# SMT solver (uses system libz3; enable z3/bundled via the prover's bundled-z3 feature for local dev without system z3) +# SMT solver (system libz3 by default; opt into vendored source or a prebuilt release) z3 = "0.21" [workspace.lints.rust] diff --git a/crates/openshell-gateway/Cargo.toml b/crates/openshell-gateway/Cargo.toml index 31e3975f0b..fa977a2c2a 100644 --- a/crates/openshell-gateway/Cargo.toml +++ b/crates/openshell-gateway/Cargo.toml @@ -73,7 +73,7 @@ telemetry = ["openshell-core/telemetry", "openshell-server/telemetry"] ## telemetry-on build. Kept in sync with `default` by ## `rust:verify:defaults-without-telemetry`. defaults-without-telemetry = ["in-tree-compute-drivers"] -bundled-z3 = ["openshell-server/bundled-z3"] +vendored-z3 = ["openshell-server/vendored-z3"] [lints] workspace = true diff --git a/crates/openshell-prover-cli/Cargo.toml b/crates/openshell-prover-cli/Cargo.toml index 337a97565d..21d324d0c7 100644 --- a/crates/openshell-prover-cli/Cargo.toml +++ b/crates/openshell-prover-cli/Cargo.toml @@ -15,7 +15,7 @@ name = "openshell-prover" path = "src/main.rs" [features] -bundled-z3 = ["openshell-prover/bundled-z3"] +vendored-z3 = ["openshell-prover/vendored-z3"] prebuilt-z3 = ["openshell-prover/prebuilt-z3"] [dependencies] diff --git a/crates/openshell-prover/Cargo.toml b/crates/openshell-prover/Cargo.toml index e39154a537..6a31be5d41 100644 --- a/crates/openshell-prover/Cargo.toml +++ b/crates/openshell-prover/Cargo.toml @@ -11,7 +11,7 @@ license.workspace = true repository.workspace = true [features] -bundled-z3 = ["z3/bundled"] +vendored-z3 = ["z3/vendored"] prebuilt-z3 = ["z3/gh-release"] [dependencies] diff --git a/crates/openshell-server/Cargo.toml b/crates/openshell-server/Cargo.toml index 54c44a7bed..30a2891f4e 100644 --- a/crates/openshell-server/Cargo.toml +++ b/crates/openshell-server/Cargo.toml @@ -120,7 +120,7 @@ default = ["telemetry"] ## On by default; build with `--no-default-features` for a telemetry-free gateway ## that contains no telemetry endpoint, HTTP client, or emission code. telemetry = ["openshell-core/telemetry"] -bundled-z3 = ["openshell-prover/bundled-z3"] +vendored-z3 = ["openshell-prover/vendored-z3"] prebuilt-z3 = ["openshell-prover/prebuilt-z3"] test-support = [] diff --git a/deploy/docker/Dockerfile.cli-macos b/deploy/docker/Dockerfile.cli-macos deleted file mode 100644 index f86cd594b2..0000000000 --- a/deploy/docker/Dockerfile.cli-macos +++ /dev/null @@ -1,131 +0,0 @@ -# syntax=docker/dockerfile:1.6 - -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Cross-compile the openshell CLI binary for macOS aarch64 (Apple Silicon) -# using the osxcross toolchain. Produces a standalone binary — no Python -# wheel wrapping. -# -# Usage: -# docker buildx build -f deploy/docker/Dockerfile.cli-macos \ -# --build-arg OPENSHELL_CARGO_VERSION=0.6.0 \ -# --output type=local,dest=out/ . - -ARG OSXCROSS_IMAGE=ghcr.io/crazy-max/osxcross:latest -ARG PYTHON_IMAGE=public.ecr.aws/docker/library/python:3.12-slim - -FROM ${OSXCROSS_IMAGE} AS osxcross - -FROM ${PYTHON_IMAGE} AS builder - -ARG CARGO_TARGET_CACHE_SCOPE=default - -ENV PATH="/root/.cargo/bin:/usr/local/bin:/osxcross/bin:${PATH}" -ENV LD_LIBRARY_PATH="/osxcross/lib" - -COPY --from=osxcross /osxcross /osxcross - -RUN SDKROOT="$(echo /osxcross/SDK/MacOSX*.sdk)" && ln -sfn "${SDKROOT}" /osxcross/SDK/MacOSX.sdk - -RUN apt-get update && apt-get install -y --no-install-recommends \ - build-essential \ - ca-certificates \ - clang \ - cmake \ - curl \ - libclang-dev \ - pkg-config \ - && rm -rf /var/lib/apt/lists/* - -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.95.0 - -# aws-lc-sys probes with --target=arm64-apple-macosx and clang then looks for -# arm64-apple-macosx-ld. Provide a linker alias to osxcross ld64. -RUN ln -sf /osxcross/bin/arm64-apple-darwin25.1-ld /usr/local/bin/arm64-apple-macosx-ld - -RUN rustup target add aarch64-apple-darwin - -WORKDIR /build - -ENV CC_aarch64_apple_darwin=oa64-clang -ENV CXX_aarch64_apple_darwin=oa64-clang++ -ENV AR_aarch64_apple_darwin=aarch64-apple-darwin25.1-ar -ENV CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER=oa64-clang -ENV CARGO_TARGET_AARCH64_APPLE_DARWIN_AR=aarch64-apple-darwin25.1-ar -ENV SDKROOT=/osxcross/SDK/MacOSX.sdk -ENV MACOSX_DEPLOYMENT_TARGET=13.3 -ENV CFLAGS_aarch64_apple_darwin=--target=arm64-apple-macosx\ -mmacosx-version-min=13.3 -ENV CXXFLAGS_aarch64_apple_darwin=--target=arm64-apple-macosx\ -mmacosx-version-min=13.3 -ENV BINDGEN_EXTRA_CLANG_ARGS_aarch64_apple_darwin=--target=arm64-apple-macosx\ -isysroot\ ${SDKROOT} - -# --------------------------------------------------------------------------- -# Stage 1: dependency caching — copy only manifests, create dummy sources, -# build dependencies. This layer is cached unless Cargo.toml/lock changes. -# --------------------------------------------------------------------------- -COPY Cargo.toml Cargo.lock ./ -COPY crates/openshell-bootstrap/Cargo.toml crates/openshell-bootstrap/Cargo.toml -COPY crates/openshell-cli/Cargo.toml crates/openshell-cli/Cargo.toml -COPY crates/openshell-core/Cargo.toml crates/openshell-core/Cargo.toml -COPY crates/openshell-policy/Cargo.toml crates/openshell-policy/Cargo.toml -COPY crates/openshell-providers/Cargo.toml crates/openshell-providers/Cargo.toml -COPY crates/openshell-tui/Cargo.toml crates/openshell-tui/Cargo.toml -COPY crates/openshell-prover/Cargo.toml crates/openshell-prover/Cargo.toml -COPY crates/openshell-core/build.rs crates/openshell-core/build.rs -COPY proto/ proto/ - -# Scope workspace to CLI crates only to avoid compiling unrelated runtimes. -RUN sed -i 's|members = \["crates/\*"\]|members = ["crates/openshell-cli", "crates/openshell-core", "crates/openshell-bootstrap", "crates/openshell-policy", "crates/openshell-prover", "crates/openshell-providers", "crates/openshell-tui"]|' Cargo.toml - -RUN mkdir -p crates/openshell-cli/src \ - crates/openshell-core/src \ - crates/openshell-bootstrap/src \ - crates/openshell-policy/src \ - crates/openshell-providers/src \ - crates/openshell-prover/src \ - crates/openshell-tui/src && \ - echo "fn main() {}" > crates/openshell-cli/src/main.rs && \ - touch crates/openshell-core/src/lib.rs && \ - touch crates/openshell-bootstrap/src/lib.rs && \ - touch crates/openshell-policy/src/lib.rs && \ - touch crates/openshell-providers/src/lib.rs && \ - touch crates/openshell-prover/src/lib.rs && \ - touch crates/openshell-tui/src/lib.rs - -RUN --mount=type=cache,id=cargo-registry-cli-macos,sharing=locked,target=/root/.cargo/registry \ - --mount=type=cache,id=cargo-git-cli-macos,sharing=locked,target=/root/.cargo/git \ - --mount=type=cache,id=cargo-target-cli-macos-${CARGO_TARGET_CACHE_SCOPE},sharing=locked,target=/build/target \ - cargo build --release --target aarch64-apple-darwin -p openshell-cli 2>/dev/null || true - -# --------------------------------------------------------------------------- -# Stage 2: real build -# --------------------------------------------------------------------------- -COPY crates/ crates/ -COPY providers/ providers/ - -# Touch source files to ensure they're rebuilt (not the cached dummy). -RUN touch crates/openshell-cli/src/main.rs \ - crates/openshell-cli/src/lib.rs \ - crates/openshell-bootstrap/src/lib.rs \ - crates/openshell-core/src/lib.rs \ - crates/openshell-policy/src/lib.rs \ - crates/openshell-providers/src/lib.rs \ - crates/openshell-tui/src/lib.rs \ - crates/openshell-core/build.rs \ - proto/*.proto - -# Declare version ARGs here (not earlier) so the git-hash-bearing values do not -# invalidate the expensive dependency-build layers above on every commit. -ARG OPENSHELL_CARGO_VERSION -ARG OPENSHELL_IMAGE_TAG -RUN --mount=type=cache,id=cargo-registry-cli-macos,sharing=locked,target=/root/.cargo/registry \ - --mount=type=cache,id=cargo-git-cli-macos,sharing=locked,target=/root/.cargo/git \ - --mount=type=cache,id=cargo-target-cli-macos-${CARGO_TARGET_CACHE_SCOPE},sharing=locked,target=/build/target \ - if [ -n "${OPENSHELL_CARGO_VERSION:-}" ]; then \ - sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${OPENSHELL_CARGO_VERSION}"'"/}' Cargo.toml; \ - fi && \ - cargo build --release --target aarch64-apple-darwin -p openshell-cli && \ - cp target/aarch64-apple-darwin/release/openshell /openshell - -FROM scratch AS binary -COPY --from=builder /openshell /openshell diff --git a/deploy/docker/Dockerfile.driver-vm-macos b/deploy/docker/Dockerfile.driver-vm-macos deleted file mode 100644 index 438700eb09..0000000000 --- a/deploy/docker/Dockerfile.driver-vm-macos +++ /dev/null @@ -1,119 +0,0 @@ -# syntax=docker/dockerfile:1.6 - -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Cross-compile the openshell-driver-vm binary for macOS aarch64 (Apple -# Silicon) using the osxcross toolchain. -# -# openshell-driver-vm loads libkrun/libkrunfw at runtime via dlopen, so it -# does NOT need Hypervisor.framework headers at build time. Pre-compressed -# runtime artifacts (libkrun, libkrunfw, bundled sandbox/supervisor) are injected via -# the vm-runtime-compressed build context and embedded into the binary via -# include_bytes!(). -# -# Usage: -# docker buildx build -f deploy/docker/Dockerfile.driver-vm-macos \ -# --build-arg OPENSHELL_CARGO_VERSION=0.6.0 \ -# --build-context vm-runtime-compressed=/path/to/compressed-dir \ -# --output type=local,dest=out/ . - -ARG OSXCROSS_IMAGE=ghcr.io/crazy-max/osxcross:latest -ARG PYTHON_IMAGE=public.ecr.aws/docker/library/python:3.12-slim - -FROM ${OSXCROSS_IMAGE} AS osxcross - -FROM ${PYTHON_IMAGE} AS builder - -ARG CARGO_TARGET_CACHE_SCOPE=default - -ENV PATH="/root/.cargo/bin:/usr/local/bin:/osxcross/bin:${PATH}" -ENV LD_LIBRARY_PATH="/osxcross/lib" - -COPY --from=osxcross /osxcross /osxcross - -RUN apt-get update && apt-get install -y --no-install-recommends \ - build-essential \ - ca-certificates \ - clang \ - cmake \ - curl \ - pkg-config \ - && rm -rf /var/lib/apt/lists/* - -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.95.0 - -RUN rustup target add aarch64-apple-darwin - -WORKDIR /build - -ENV CC_aarch64_apple_darwin=oa64-clang -ENV CXX_aarch64_apple_darwin=oa64-clang++ -ENV AR_aarch64_apple_darwin=aarch64-apple-darwin25.1-ar -ENV CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER=oa64-clang -ENV CARGO_TARGET_AARCH64_APPLE_DARWIN_AR=aarch64-apple-darwin25.1-ar - -# aws-lc-sys workaround (in case it ends up in the dep tree via feature unification) -RUN ln -sf /osxcross/bin/arm64-apple-darwin25.1-ld /usr/local/bin/arm64-apple-macosx-ld - -# --------------------------------------------------------------------------- -# Stage 1: dependency caching — copy only manifests, create dummy sources, -# build dependencies. This layer is cached unless Cargo.toml/lock changes. -# --------------------------------------------------------------------------- -COPY Cargo.toml Cargo.lock ./ -COPY crates/openshell-driver-vm/Cargo.toml crates/openshell-driver-vm/Cargo.toml -COPY crates/openshell-driver-vm/build.rs crates/openshell-driver-vm/build.rs -COPY crates/openshell-core/Cargo.toml crates/openshell-core/Cargo.toml -COPY crates/openshell-core/build.rs crates/openshell-core/build.rs -COPY proto/ proto/ - -# Scope workspace to the driver + its only internal dep. -RUN sed -i 's|members = \["crates/\*"\]|members = ["crates/openshell-driver-vm", "crates/openshell-core"]|' Cargo.toml - -RUN mkdir -p crates/openshell-driver-vm/src \ - crates/openshell-core/src && \ - echo "fn main() {}" > crates/openshell-driver-vm/src/main.rs && \ - touch crates/openshell-driver-vm/src/lib.rs && \ - touch crates/openshell-core/src/lib.rs - -# Build deps only (cached layer). The 2>/dev/null || true is a warm-cache -# technique; real source is copied in stage 2. -RUN --mount=type=cache,id=cargo-registry-driver-vm-macos,sharing=locked,target=/root/.cargo/registry \ - --mount=type=cache,id=cargo-git-driver-vm-macos,sharing=locked,target=/root/.cargo/git \ - --mount=type=cache,id=cargo-target-driver-vm-macos-${CARGO_TARGET_CACHE_SCOPE},sharing=locked,target=/build/target \ - cargo build --release --target aarch64-apple-darwin -p openshell-driver-vm 2>/dev/null || true - -# --------------------------------------------------------------------------- -# Stage 2: real build with compressed runtime artifacts -# --------------------------------------------------------------------------- -COPY crates/ crates/ - -# Copy compressed VM runtime artifacts for embedding. -# These are passed in via --build-context vm-runtime-compressed=... -COPY --from=vm-runtime-compressed / /build/vm-runtime-compressed/ - -# Touch source files to ensure they're rebuilt (not the cached dummy). -RUN touch crates/openshell-driver-vm/src/main.rs \ - crates/openshell-driver-vm/src/lib.rs \ - crates/openshell-driver-vm/build.rs \ - crates/openshell-core/src/lib.rs \ - crates/openshell-core/build.rs \ - proto/*.proto - -# Declare version ARGs here (not earlier) so the git-hash-bearing values do not -# invalidate the expensive dependency-build layers above on every commit. -ARG OPENSHELL_CARGO_VERSION -ARG OPENSHELL_IMAGE_TAG -RUN --mount=type=cache,id=cargo-registry-driver-vm-macos,sharing=locked,target=/root/.cargo/registry \ - --mount=type=cache,id=cargo-git-driver-vm-macos,sharing=locked,target=/root/.cargo/git \ - --mount=type=cache,id=cargo-target-driver-vm-macos-${CARGO_TARGET_CACHE_SCOPE},sharing=locked,target=/build/target \ - if [ -n "${OPENSHELL_CARGO_VERSION:-}" ]; then \ - sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${OPENSHELL_CARGO_VERSION}"'"/}' Cargo.toml; \ - fi && \ - OPENSHELL_VM_RUNTIME_COMPRESSED_DIR=/build/vm-runtime-compressed \ - OPENSHELL_IMAGE_TAG="${OPENSHELL_IMAGE_TAG:-dev}" \ - cargo build --release --target aarch64-apple-darwin -p openshell-driver-vm && \ - cp target/aarch64-apple-darwin/release/openshell-driver-vm /openshell-driver-vm - -FROM scratch AS binary -COPY --from=builder /openshell-driver-vm /openshell-driver-vm diff --git a/deploy/docker/Dockerfile.gateway-macos b/deploy/docker/Dockerfile.gateway-macos deleted file mode 100644 index 4ecbac0397..0000000000 --- a/deploy/docker/Dockerfile.gateway-macos +++ /dev/null @@ -1,112 +0,0 @@ -# syntax=docker/dockerfile:1.6 - -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Cross-compile the standalone openshell-gateway binary for macOS aarch64 -# (Apple Silicon) using the osxcross toolchain. - -ARG OSXCROSS_IMAGE=ghcr.io/crazy-max/osxcross:latest -ARG PYTHON_IMAGE=public.ecr.aws/docker/library/python:3.12-slim - -FROM ${OSXCROSS_IMAGE} AS osxcross - -FROM ${PYTHON_IMAGE} AS builder - -ARG CARGO_TARGET_CACHE_SCOPE=default - -ENV PATH="/root/.cargo/bin:/usr/local/bin:/osxcross/bin:${PATH}" -ENV LD_LIBRARY_PATH="/osxcross/lib" - -COPY --from=osxcross /osxcross /osxcross - -RUN SDKROOT="$(echo /osxcross/SDK/MacOSX*.sdk)" && ln -sfn "${SDKROOT}" /osxcross/SDK/MacOSX.sdk - -RUN apt-get update && apt-get install -y --no-install-recommends \ - build-essential \ - ca-certificates \ - clang \ - cmake \ - curl \ - libclang-dev \ - pkg-config \ - && rm -rf /var/lib/apt/lists/* - -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.95.0 - -RUN ln -sf /osxcross/bin/arm64-apple-darwin25.1-ld /usr/local/bin/arm64-apple-macosx-ld - -RUN rustup target add aarch64-apple-darwin - -WORKDIR /build - -ENV CC_aarch64_apple_darwin=oa64-clang -ENV CXX_aarch64_apple_darwin=oa64-clang++ -ENV AR_aarch64_apple_darwin=aarch64-apple-darwin25.1-ar -ENV CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER=oa64-clang -ENV CARGO_TARGET_AARCH64_APPLE_DARWIN_AR=aarch64-apple-darwin25.1-ar -ENV SDKROOT=/osxcross/SDK/MacOSX.sdk -ENV MACOSX_DEPLOYMENT_TARGET=13.3 -ENV CFLAGS_aarch64_apple_darwin=--target=arm64-apple-macosx\ -mmacosx-version-min=13.3 -ENV CXXFLAGS_aarch64_apple_darwin=--target=arm64-apple-macosx\ -mmacosx-version-min=13.3 -ENV BINDGEN_EXTRA_CLANG_ARGS_aarch64_apple_darwin=--target=arm64-apple-macosx\ -isysroot\ ${SDKROOT} - -COPY Cargo.toml Cargo.lock ./ -COPY crates/openshell-core/Cargo.toml crates/openshell-core/Cargo.toml -COPY crates/openshell-gateway/Cargo.toml crates/openshell-gateway/Cargo.toml -COPY crates/openshell-driver-kubernetes/Cargo.toml crates/openshell-driver-kubernetes/Cargo.toml -COPY crates/openshell-policy/Cargo.toml crates/openshell-policy/Cargo.toml -COPY crates/openshell-prover/Cargo.toml crates/openshell-prover/Cargo.toml -COPY crates/openshell-server/Cargo.toml crates/openshell-server/Cargo.toml -COPY crates/openshell-core/build.rs crates/openshell-core/build.rs -COPY proto/ proto/ - -RUN sed -i 's|members = \["crates/\*"\]|members = ["crates/openshell-gateway", "crates/openshell-server", "crates/openshell-core", "crates/openshell-driver-kubernetes", "crates/openshell-policy", "crates/openshell-prover"]|' Cargo.toml - -RUN mkdir -p crates/openshell-core/src \ - crates/openshell-gateway/src \ - crates/openshell-driver-kubernetes/src \ - crates/openshell-policy/src \ - crates/openshell-prover/src \ - crates/openshell-server/src && \ - touch crates/openshell-core/src/lib.rs && \ - touch crates/openshell-gateway/src/lib.rs && \ - printf 'fn main() {}\n' > crates/openshell-gateway/src/main.rs && \ - touch crates/openshell-driver-kubernetes/src/lib.rs && \ - printf 'fn main() {}\n' > crates/openshell-driver-kubernetes/src/main.rs && \ - touch crates/openshell-policy/src/lib.rs && \ - touch crates/openshell-prover/src/lib.rs && \ - touch crates/openshell-server/src/lib.rs - -RUN --mount=type=cache,id=cargo-registry-gateway-macos,sharing=locked,target=/root/.cargo/registry \ - --mount=type=cache,id=cargo-git-gateway-macos,sharing=locked,target=/root/.cargo/git \ - --mount=type=cache,id=cargo-target-gateway-macos-${CARGO_TARGET_CACHE_SCOPE},sharing=locked,target=/build/target \ - cargo build --release --target aarch64-apple-darwin -p openshell-gateway --features bundled-z3 2>/dev/null || true - -COPY crates/ crates/ -COPY providers/ providers/ - -RUN touch crates/openshell-core/src/lib.rs \ - crates/openshell-gateway/src/lib.rs \ - crates/openshell-gateway/src/main.rs \ - crates/openshell-driver-kubernetes/src/lib.rs \ - crates/openshell-driver-kubernetes/src/main.rs \ - crates/openshell-policy/src/lib.rs \ - crates/openshell-prover/src/lib.rs \ - crates/openshell-server/src/lib.rs \ - crates/openshell-core/build.rs \ - proto/*.proto - -ARG OPENSHELL_CARGO_VERSION -ARG OPENSHELL_IMAGE_TAG -RUN --mount=type=cache,id=cargo-registry-gateway-macos,sharing=locked,target=/root/.cargo/registry \ - --mount=type=cache,id=cargo-git-gateway-macos,sharing=locked,target=/root/.cargo/git \ - --mount=type=cache,id=cargo-target-gateway-macos-${CARGO_TARGET_CACHE_SCOPE},sharing=locked,target=/build/target \ - if [ -n "${OPENSHELL_CARGO_VERSION:-}" ]; then \ - sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${OPENSHELL_CARGO_VERSION}"'"/}' Cargo.toml; \ - fi && \ - cargo build --release --target aarch64-apple-darwin -p openshell-gateway --features bundled-z3 && \ - cp target/aarch64-apple-darwin/release/openshell-gateway /openshell-gateway - -FROM scratch AS binary -COPY --from=builder /openshell-gateway /openshell-gateway diff --git a/docs/observability/telemetry.mdx b/docs/observability/telemetry.mdx index e78e0f7d00..1d40772d88 100644 --- a/docs/observability/telemetry.mdx +++ b/docs/observability/telemetry.mdx @@ -46,8 +46,8 @@ cargo build --release -p openshell-gateway --no-default-features --features tele # Docker and VM only, with telemetry compiled out. cargo build --release -p openshell-gateway --no-default-features --features compute-driver-docker,compute-driver-vm -# Windows MXC only, with telemetry support and bundled Z3. -cargo build --release -p openshell-gateway --no-default-features --features telemetry,compute-driver-mxc,bundled-z3 +# Windows MXC only, with telemetry support and prebuilt Z3. +cargo build --release -p openshell-gateway --no-default-features --features telemetry,compute-driver-mxc,openshell-server/prebuilt-z3 ``` Regular builds keep their platform driver set through the default `in-tree-compute-drivers` compatibility feature. On Windows, `compute-driver-mxc` selects MXC and the other four features install unsupported-driver stubs. On other platforms, MXC is excluded. diff --git a/e2e/run.sh b/e2e/run.sh index 5cd9a5be78..f96d06cfdb 100755 --- a/e2e/run.sh +++ b/e2e/run.sh @@ -264,7 +264,7 @@ if [ "${mode}" = host ]; then mise x -- cargo build "${cargo_jobs[@]}" \ -p openshell-gateway \ --bin openshell-gateway \ - --features bundled-z3 + --features vendored-z3 host_gateway_bin="${target_dir}/debug/openshell-gateway" else echo "==> Building Linux openshell-gateway (${linux_gateway_zig_target})" @@ -280,7 +280,7 @@ else --target "${linux_gateway_zig_target}" \ -p openshell-gateway \ --bin openshell-gateway \ - --features bundled-z3 + --features vendored-z3 ) guest_gateway_bin="${target_dir}/${linux_gateway_rust_target}/release/openshell-gateway" fi diff --git a/e2e/with-kube-gateway.sh b/e2e/with-kube-gateway.sh index c4ace7ae89..a400ec29fe 100755 --- a/e2e/with-kube-gateway.sh +++ b/e2e/with-kube-gateway.sh @@ -1049,12 +1049,9 @@ if [ "${OPENSHELL_E2E_KUBE_BUILD_IMAGES}" = "1" ]; then fi external_gateway="${OPENSHELL_GATEWAY_BIN:-${ROOT}/target/debug/openshell-gateway}" external_driver="${OPENSHELL_EXTERNAL_DRIVER_BIN:-${ROOT}/target/debug/openshell-driver-kubernetes}" - # The test image uses a distroless runtime, so keep Z3 self-contained just - # like the production gateway image artifact. A host-linked debug binary - # would otherwise require libz3.so from the CI build machine at runtime. if [ -z "${OPENSHELL_GATEWAY_BIN:-}" ]; then cargo build -p openshell-gateway --bin openshell-gateway \ - --no-default-features --features telemetry,bundled-z3 + --no-default-features --features telemetry,vendored-z3 fi if [ -z "${OPENSHELL_EXTERNAL_DRIVER_BIN:-}" ]; then cargo build -p openshell-driver-kubernetes --bin openshell-driver-kubernetes diff --git a/tasks/ci.toml b/tasks/ci.toml index e2a388e6b1..7f896c570d 100644 --- a/tasks/ci.toml +++ b/tasks/ci.toml @@ -31,7 +31,7 @@ hide = true description = "Build release Rust binaries consumed by the hand-staged snap" run = [ "cargo build --release -p openshell-cli", - "cargo build --release -p openshell-gateway --features bundled-z3", + "cargo build --release -p openshell-gateway", "cargo build --release -p openshell-sandbox", ] diff --git a/tasks/scripts/setup-zig-cc-wrapper.sh b/tasks/scripts/setup-zig-cc-wrapper.sh index c21e0a3487..3ec27868ca 100755 --- a/tasks/scripts/setup-zig-cc-wrapper.sh +++ b/tasks/scripts/setup-zig-cc-wrapper.sh @@ -94,26 +94,6 @@ set(CMAKE_RANLIB "$wrapper_dir/ranlib") set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) EOF -is_stale_z3_build_dir() { - local build_dir=$1 - - grep -R -q -E \ - 'cargo-zigbuild|zigc(c|xx)-.*unknown-linux-gnu\.[0-9]+\.[0-9]+' \ - "$build_dir/CMakeCache.txt" "$build_dir/CMakeFiles" 2>/dev/null -} - -for profile in release debug; do - z3_build_root="target/$bare_cargo_target/$profile/build" - if [[ -d $z3_build_root ]]; then - while IFS= read -r z3_build_dir; do - if is_stale_z3_build_dir "$z3_build_dir"; then - echo "Removing stale z3-sys CMake cache: $z3_build_dir" >&2 - rm -rf "$z3_build_dir" - fi - done < <(find "$z3_build_root" -mindepth 3 -maxdepth 3 -type d -path "*/z3-sys-*/out/build") - fi -done - target_env=${cargo_target//[-.]/_} bare_target_env=${bare_cargo_target//[-.]/_} diff --git a/tasks/scripts/stage-prebuilt-binaries.sh b/tasks/scripts/stage-prebuilt-binaries.sh index 312cec7a91..01d2bff12f 100755 --- a/tasks/scripts/stage-prebuilt-binaries.sh +++ b/tasks/scripts/stage-prebuilt-binaries.sh @@ -167,8 +167,8 @@ build_component_for_arch() { target="$(target_triple "$arch" "$target_libc")" stage="${ROOT}/deploy/docker/.build/prebuilt-binaries/${arch}" features="${EXTRA_CARGO_FEATURES:-}" - if [[ "$component" == "gateway" && " ${features} " != *" bundled-z3 "* ]]; then - features="${features} bundled-z3" + if [[ "$component" == "gateway" ]]; then + features="${features} vendored-z3" fi current_host_os="$(host_os)" current_host_arch="$(host_arch)" diff --git a/tasks/scripts/trivy-scan.sh b/tasks/scripts/trivy-scan.sh index 5d2fd85f75..fbfe6d47c1 100755 --- a/tasks/scripts/trivy-scan.sh +++ b/tasks/scripts/trivy-scan.sh @@ -24,7 +24,6 @@ PREFLIGHT_OFF=(--helm-set agentSandbox.preflight.enabled=false) # These Dockerfiles do not produce release runtime images. SKIP_DOCKERFILES=( --skip-files 'deploy/docker/Dockerfile.ci' - --skip-files 'deploy/docker/Dockerfile.*-macos' ) # Explicit OpenShell variants, including dev/E2E regression coverage. From fe38637533962317c05a728833a691d0472ce255 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Thu, 1 Oct 2026 12:47:20 +0000 Subject: [PATCH 17/33] fix(runtime): recover SSH relays and bound startup diagnostics (#4011) * fix(runtime): recover SSH relays and bound startup diagnostics Signed-off-by: Drew Newberry * fix(server): deliver pending relays once per supervisor session Signed-off-by: Drew Newberry * fix(server): satisfy relay delivery clippy diagnostics Signed-off-by: Drew Newberry * fix(server): bound relay setup with one absolute deadline Signed-off-by: Drew Newberry --------- Signed-off-by: Drew Newberry --- crates/openshell-cli/src/ssh.rs | 51 +- .../tests/ssh_proxy_shutdown_integration.rs | 117 ++++ crates/openshell-driver-docker/src/lib.rs | 11 + crates/openshell-driver-docker/src/tests.rs | 26 + crates/openshell-server/Cargo.toml | 1 + .../src/supervisor_session.rs | 559 +++++++++++++----- 6 files changed, 607 insertions(+), 158 deletions(-) create mode 100644 crates/openshell-cli/tests/ssh_proxy_shutdown_integration.rs diff --git a/crates/openshell-cli/src/ssh.rs b/crates/openshell-cli/src/ssh.rs index 8d818062ca..a7dc992e07 100644 --- a/crates/openshell-cli/src/ssh.rs +++ b/crates/openshell-cli/src/ssh.rs @@ -25,7 +25,7 @@ use std::os::unix::process::CommandExt; use std::path::{Path, PathBuf}; use std::process::{Command, ExitStatus, Stdio}; use std::time::{Duration, Instant}; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::io::AsyncWriteExt; use tokio::net::TcpStream; use tokio::process::{Child, Command as TokioCommand}; use tokio_stream::wrappers::ReceiverStream; @@ -1926,20 +1926,37 @@ pub async fn sandbox_ssh_proxy( .into_diagnostic()? .into_inner(); - let stdin = tokio::io::stdin(); let stdout = tokio::io::stdout(); - let to_remote = tokio::spawn(async move { - let mut stdin = stdin; - let mut buf = vec![0u8; 64 * 1024]; - while let Ok(n) = stdin.read(&mut buf).await { - if n == 0 { - break; + // Tokio stdin uses an uncancellable read on the runtime's blocking pool. + // If the relay closes while SSH still holds the pipe open, runtime shutdown + // would wait forever for that read. A dedicated thread can be left behind + // when this ProxyCommand process exits without holding the runtime alive. + let (stdin_tx, mut stdin_rx) = tokio::sync::mpsc::channel(8); + std::thread::Builder::new() + .name("ssh-proxy-stdin".into()) + .spawn(move || { + use std::io::Read as _; + let mut stdin = std::io::stdin().lock(); + let mut buf = vec![0u8; 64 * 1024]; + loop { + match stdin.read(&mut buf) { + Ok(0) | Err(_) => break, + Ok(n) => { + if stdin_tx.blocking_send(buf[..n].to_vec()).is_err() { + break; + } + } + } } + }) + .into_diagnostic()?; + let to_remote = tokio::spawn(async move { + while let Some(data) = stdin_rx.recv().await { if tx .send(TcpForwardFrame { payload: Some(openshell_core::proto::tcp_forward_frame::Payload::Data( - buf[..n].to_vec(), + data, )), }) .await @@ -1952,8 +1969,10 @@ pub async fn sandbox_ssh_proxy( let from_remote = tokio::spawn(async move { let mut stdout = stdout; loop { - let Ok(Some(frame)) = response.message().await else { - break; + let frame = match response.message().await { + Ok(Some(frame)) => frame, + Ok(None) => return Ok::<_, Report>(()), + Err(error) => return Err(error).into_diagnostic(), }; let Some(openshell_core::proto::tcp_forward_frame::Payload::Data(data)) = frame.payload else { @@ -1962,16 +1981,14 @@ pub async fn sandbox_ssh_proxy( if data.is_empty() { continue; } - if stdout.write_all(&data).await.is_err() { - break; - } - let _ = stdout.flush().await; + stdout.write_all(&data).await.into_diagnostic()?; + stdout.flush().await.into_diagnostic()?; } }); - let _ = from_remote.await; + let result = from_remote.await; to_remote.abort(); - Ok(()) + result.into_diagnostic()? } fn grpc_server_from_ssh_gateway_url(gateway_url: &str) -> Result { diff --git a/crates/openshell-cli/tests/ssh_proxy_shutdown_integration.rs b/crates/openshell-cli/tests/ssh_proxy_shutdown_integration.rs new file mode 100644 index 0000000000..76f9b32f5e --- /dev/null +++ b/crates/openshell-cli/tests/ssh_proxy_shutdown_integration.rs @@ -0,0 +1,117 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +use bytes::Bytes; +use http_body_util::{BodyExt, StreamBody}; +use hyper::body::Frame; +use hyper::service::service_fn; +use hyper_util::rt::{TokioExecutor, TokioIo}; +use openshell_core::proto::{TcpForwardFrame, tcp_forward_frame}; +use prost::Message; +use std::convert::Infallible; +use std::process::Stdio; +use std::time::Duration; +use tokio::io::AsyncWriteExt; + +async fn proxy_exits_with_stdin_open(relay_status: tonic::Status) { + let expected_code = relay_status.code(); + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let address = listener.local_addr().unwrap(); + let server = tokio::spawn(async move { + let (socket, _) = listener.accept().await.unwrap(); + let service = service_fn(move |request: hyper::Request| { + let status = relay_status.clone(); + async move { + assert_eq!(request.uri().path(), "/openshell.v1.OpenShell/ForwardTcp"); + let body = StreamBody::new(futures::stream::once(async move { + // Wait until stdin actually traverses the proxy. After this + // chunk its reader blocks again, with the parent pipe open. + let mut inbound = request.into_body(); + let mut pending = Vec::new(); + 'input: while let Some(frame) = inbound.frame().await { + if let Ok(data) = frame.unwrap().into_data() { + pending.extend_from_slice(&data); + while pending.len() >= 5 { + let length = + u32::from_be_bytes(pending[1..5].try_into().unwrap()) as usize; + if pending.len() < 5 + length { + break; + } + let frame = + TcpForwardFrame::decode(&pending[5..5 + length]).unwrap(); + pending.drain(..5 + length); + if matches!(frame.payload, Some(tcp_forward_frame::Payload::Data(data)) if data == b"probe") + { + break 'input; + } + } + } + } + tokio::time::sleep(Duration::from_millis(100)).await; + let trailers = status.into_http::<()>().into_parts().0.headers; + Ok::, Infallible>(Frame::trailers(trailers)) + })); + let mut response = hyper::Response::new(body); + response + .headers_mut() + .insert("content-type", "application/grpc".parse().unwrap()); + Ok::<_, Infallible>(response) + } + }); + let _ = hyper::server::conn::http2::Builder::new(TokioExecutor::new()) + .serve_connection(TokioIo::new(socket), service) + .await; + }); + let config = tempfile::tempdir().unwrap(); + let mut child = tokio::process::Command::new(env!("CARGO_BIN_EXE_openshell")) + .args([ + "ssh-proxy", + "--gateway", + &format!("http://{address}/proxy/connect"), + "--sandbox", + "repro", + "--token", + "test-token", + ]) + .env("XDG_CONFIG_HOME", config.path()) + .env("OPENSHELL_TELEMETRY_ENABLED", "false") + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .kill_on_drop(true) + .spawn() + .unwrap(); + child + .stdin + .as_mut() + .unwrap() + .write_all(b"probe") + .await + .unwrap(); + // Keep child.stdin alive throughout wait: closing it would conceal the bug. + let status = tokio::time::timeout(Duration::from_secs(5), child.wait()) + .await + .expect("SSH proxy must exit even while the parent holds stdin open") + .unwrap(); + let mut stderr = String::new(); + tokio::io::AsyncReadExt::read_to_string(child.stderr.as_mut().unwrap(), &mut stderr) + .await + .unwrap(); + if expected_code == tonic::Code::Ok { + assert!(status.success(), "{stderr}"); + } else { + assert!(!status.success(), "relay failure must propagate to SSH"); + assert!(stderr.contains("Deadline expired"), "{stderr}"); + } + server.abort(); +} + +#[tokio::test] +async fn proxy_exits_after_relay_error_with_stdin_open() { + proxy_exits_with_stdin_open(tonic::Status::deadline_exceeded("relay open timed out")).await; +} + +#[tokio::test] +async fn proxy_exits_after_clean_relay_close_with_stdin_open() { + proxy_exits_with_stdin_open(tonic::Status::ok("")).await; +} diff --git a/crates/openshell-driver-docker/src/lib.rs b/crates/openshell-driver-docker/src/lib.rs index 4c9d2ac2bc..3f839876e9 100644 --- a/crates/openshell-driver-docker/src/lib.rs +++ b/crates/openshell-driver-docker/src/lib.rs @@ -5362,6 +5362,7 @@ async fn wait_for_docker_supervisor_ready( _ if state.running == Some(false) => { let log_tail = docker_container_log_tail(docker, supervisor_id).await; let sandbox_log_tail = docker_container_log_tail(docker, sandbox_id).await; + warn!(sandbox_id, supervisor_id, supervisor_logs = %log_tail, sandbox_logs = %sandbox_log_tail, "Docker supervisor exited before becoming ready"); return Err(Status::unavailable(format!( "Docker supervisor exited before becoming ready{}{}", format_log_tail(&log_tail), @@ -5378,8 +5379,18 @@ fn format_log_tail(log_tail: &str) -> String { } fn format_named_log_tail(label: &str, log_tail: &str) -> String { + // gRPC status messages travel in HTTP/2 headers. Two 16 KiB container + // tails exceed the client's 16 KiB header budget and hide the real error + // behind PROTOCOL_ERROR. Allow for up to 3x percent-encoding expansion. + const MAX_STATUS_LOG_TAIL_BYTES: usize = 1024; if log_tail.is_empty() { String::new() + } else if log_tail.len() > MAX_STATUS_LOG_TAIL_BYTES { + let mut start = log_tail.len() - MAX_STATUS_LOG_TAIL_BYTES; + while !log_tail.is_char_boundary(start) { + start += 1; + } + format!("; {label}: [truncated] {}", &log_tail[start..]) } else { format!("; {label}: {log_tail}") } diff --git a/crates/openshell-driver-docker/src/tests.rs b/crates/openshell-driver-docker/src/tests.rs index 8aa05302e0..f09b2c6f91 100644 --- a/crates/openshell-driver-docker/src/tests.rs +++ b/crates/openshell-driver-docker/src/tests.rs @@ -24,6 +24,32 @@ use std::io::Read as _; use std::sync::Arc; use tempfile::TempDir; +#[test] +fn startup_error_log_tails_fit_grpc_header_budget() { + // Multibyte text exercises both the UTF-8 cut and worst-case gRPC message + // percent encoding. Preserve the final diagnostic from each container. + let logs = format!("{}\nstartup timed out", "🦀".repeat(8192)); + let message = format!( + "Docker supervisor exited before becoming ready{}{}", + format_log_tail(&logs), + format_named_log_tail("sandbox log tail", &logs), + ); + assert_eq!(message.matches("[truncated]").count(), 2); + assert_eq!(message.matches("startup timed out").count(), 2); + let response = Status::unavailable(message).into_http::<()>(); + let header_bytes: usize = response + .headers() + .iter() + .map(|(name, value)| name.as_str().len() + value.as_bytes().len() + 32) + .sum(); + assert!( + header_bytes < 16 * 1024, + "status headers: {header_bytes} bytes" + ); + assert_eq!(format_log_tail("small error"), "; log tail: small error"); + assert!(format_log_tail("").is_empty()); +} + fn test_launch_authentication() -> Vec { serde_json::to_vec(&SandboxLaunchAuthentication { supervisor: SupervisorAuthBundle { diff --git a/crates/openshell-server/Cargo.toml b/crates/openshell-server/Cargo.toml index 30a2891f4e..1c206b70ec 100644 --- a/crates/openshell-server/Cargo.toml +++ b/crates/openshell-server/Cargo.toml @@ -129,6 +129,7 @@ tonic-prost-build = { workspace = true } protoc-bin-vendored = { workspace = true } [dev-dependencies] +tokio = { workspace = true, features = ["test-util"] } # Tests import the example profiles from providers/ the way an operator # would; the feature is test-only and never reaches a release binary. openshell-providers = { path = "../openshell-providers", features = ["example-profiles"] } diff --git a/crates/openshell-server/src/supervisor_session.rs b/crates/openshell-server/src/supervisor_session.rs index 847d625a28..7fc11594af 100644 --- a/crates/openshell-server/src/supervisor_session.rs +++ b/crates/openshell-server/src/supervisor_session.rs @@ -336,6 +336,8 @@ struct PendingRelay { sandbox_id: String, relay_open: RelayOpen, created_at: Instant, + /// Last session whose outbound queue received this `RelayOpen`. + delivered_session_id: Option, } #[derive(Debug)] @@ -466,23 +468,28 @@ impl SupervisorSessionRegistry { None } - /// Look up the sender for a supervisor session, waiting up to `timeout` - /// for it to appear if absent. + /// Look up a supervisor session without extending the setup deadline. /// /// Uses exponential backoff (100ms → 2s) while polling the sessions map. async fn wait_for_session( &self, sandbox_id: &str, - timeout: Duration, - ) -> Result, Status> { - let deadline = Instant::now() + timeout; + deadline: tokio::time::Instant, + wait_for_missing_session: bool, + ) -> Result<(String, mpsc::Sender), Status> { let mut backoff = SESSION_WAIT_INITIAL_BACKOFF; loop { - if let Some(tx) = self.lookup_session(sandbox_id) { - return Ok(tx); + let session = self + .sessions + .lock() + .unwrap() + .get(sandbox_id) + .map(|session| (session.session_id.clone(), session.tx.clone())); + if let Some(session) = session { + return Ok(session); } - if Instant::now() + backoff > deadline { + if !wait_for_missing_session || tokio::time::Instant::now() + backoff > deadline { return Err(Status::unavailable("supervisor session not connected")); } tokio::time::sleep(backoff).await; @@ -490,6 +497,7 @@ impl SupervisorSessionRegistry { } } + #[cfg(test)] fn lookup_session(&self, sandbox_id: &str) -> Option> { self.sessions .lock() @@ -728,7 +736,8 @@ impl SupervisorSessionRegistry { /// blip, gateway restart, supervisor restart) and the supervisor is /// in its reconnect backoff loop /// - /// Callers pick the timeout based on how much patience the caller needs. + /// The timeout bounds session lookup, reconnect retries, and outbound queue + /// capacity waits together. Callers pick it based on their patience. /// A first `sandbox connect` right after `sandbox create` may need to /// wait for the supervisor's initial TLS + gRPC handshake (tens of /// seconds on a slow cluster), while mid-lifetime calls typically just @@ -787,59 +796,107 @@ impl SupervisorSessionRegistry { oneshot::Receiver>, ), Status, + > { + self.open_relay_with_message_until( + sandbox_id, + relay_open, + tokio::time::Instant::now() + session_wait_timeout, + true, + ) + .await + } + + /// Local routing skips waiting for a missing session, but queue capacity + /// and reconnect retries still share the routing caller's setup deadline. + async fn open_relay_with_message_until( + &self, + sandbox_id: &str, + relay_open: RelayOpen, + deadline: tokio::time::Instant, + wait_for_missing_session: bool, + ) -> Result< + ( + String, + oneshot::Receiver>, + ), + Status, > { if relay_open.channel_id.is_empty() { return Err(Status::invalid_argument("relay channel_id is required")); } - let tx = self - .wait_for_session(sandbox_id, session_wait_timeout) - .await?; - - let channel_id = relay_open.channel_id.clone(); - - // Register the pending relay before sending RelayOpen to avoid a race. - // Both caps are checked and the insert happens under a single lock hold - // so two concurrent calls can't both observe "under the cap" and then - // both insert past it. - let (relay_tx, relay_rx) = oneshot::channel(); - { - let mut pending = self.pending_relays.lock().unwrap(); - if pending.len() >= MAX_PENDING_RELAYS { - return Err(Status::resource_exhausted(format!( - "gateway relay capacity reached ({MAX_PENDING_RELAYS} in flight)" - ))); - } - let per_sandbox = pending - .values() - .filter(|p| p.sandbox_id == sandbox_id) - .count(); - if per_sandbox >= MAX_PENDING_RELAYS_PER_SANDBOX { - return Err(Status::resource_exhausted(format!( - "per-sandbox relay limit reached ({MAX_PENDING_RELAYS_PER_SANDBOX} in flight for {sandbox_id})" - ))); + tokio::time::timeout_at(deadline, async { + let channel_id = relay_open.channel_id.clone(); + + // Register the pending relay before sending RelayOpen to avoid a race. + // Both caps are checked and the insert happens under a single lock hold + // so two concurrent calls can't both observe "under the cap" and then + // both insert past it. + let (relay_tx, relay_rx) = oneshot::channel(); + let mut relay_tx = Some(relay_tx); + loop { + if tokio::time::Instant::now() >= deadline { + return Err(Status::deadline_exceeded("supervisor relay setup timed out")); + } + let (session_id, tx) = self + .wait_for_session(sandbox_id, deadline, wait_for_missing_session) + .await?; + // Reserve capacity before taking synchronous locks. The session + // may change while waiting; validate it again before insertion. + let permit = tx + .reserve() + .await + .map_err(|_| Status::unavailable("supervisor session disconnected"))?; + if tokio::time::Instant::now() >= deadline { + return Err(Status::deadline_exceeded("supervisor relay setup timed out")); + } + let sent = { + let sessions = self.sessions.lock().unwrap(); + if sessions + .get(sandbox_id) + .is_some_and(|session| session.session_id == session_id) + { + let mut pending = self.pending_relays.lock().unwrap(); + if pending.len() >= MAX_PENDING_RELAYS { + return Err(Status::resource_exhausted(format!( + "gateway relay capacity reached ({MAX_PENDING_RELAYS} in flight)" + ))); + } + let per_sandbox = pending + .values() + .filter(|p| p.sandbox_id == sandbox_id) + .count(); + if per_sandbox >= MAX_PENDING_RELAYS_PER_SANDBOX { + return Err(Status::resource_exhausted(format!( + "per-sandbox relay limit reached ({MAX_PENDING_RELAYS_PER_SANDBOX} in flight for {sandbox_id})" + ))); + } + pending.insert( + channel_id.clone(), + PendingRelay { + sender: relay_tx.take().unwrap(), + sandbox_id: sandbox_id.to_string(), + relay_open: relay_open.clone(), + created_at: Instant::now(), + delivered_session_id: Some(session_id), + }, + ); + // Insertion, delivery selection, and enqueueing are atomic with + // respect to registration and replay. No await holds these locks. + permit.send(GatewayMessage { + payload: Some(gateway_message::Payload::RelayOpen(relay_open.clone())), + }); + true + } else { + false + } + }; + if sent { + return Ok((channel_id, relay_rx)); + } } - pending.insert( - channel_id.clone(), - PendingRelay { - sender: relay_tx, - sandbox_id: sandbox_id.to_string(), - relay_open: relay_open.clone(), - created_at: Instant::now(), - }, - ); - } - - let msg = GatewayMessage { - payload: Some(gateway_message::Payload::RelayOpen(relay_open)), - }; - - if tx.send(msg).await.is_err() { - // Session dropped between our lookup and send. - self.pending_relays.lock().unwrap().remove(&channel_id); - return Err(Status::unavailable("supervisor session disconnected")); - } - - Ok((channel_id, relay_rx)) + }) + .await + .map_err(|_| Status::deadline_exceeded("supervisor relay setup timed out"))? } pub fn fail_pending_relay(&self, channel_id: &str, error: String) -> bool { @@ -918,24 +975,44 @@ impl SupervisorSessionRegistry { self.remove(sandbox_id); } - pub async fn replay_pending_relays(&self, sandbox_id: &str, tx: &mpsc::Sender) { + pub async fn replay_pending_relays( + &self, + sandbox_id: &str, + session_id: &str, + tx: &mpsc::Sender, + ) { for channel_id in self.pending_channel_ids(sandbox_id) { - let relay_open = { + let needs_replay = { let pending = self.pending_relays.lock().unwrap(); - pending - .get(&channel_id) - .map(|pending| pending.relay_open.clone()) + pending.get(&channel_id).is_some_and(|pending| { + pending.delivered_session_id.as_deref() != Some(session_id) + }) }; - let Some(relay_open) = relay_open else { + if !needs_replay { continue; + } + let Ok(permit) = tx.reserve().await else { + warn!(sandbox_id = %sandbox_id, channel_id = %channel_id, "supervisor session: failed to replay pending relay to connected session"); + break; }; - let msg = GatewayMessage { - payload: Some(gateway_message::Payload::RelayOpen(relay_open)), - }; - if tx.send(msg).await.is_err() { - warn!(sandbox_id = %sandbox_id, channel_id = %channel_id, "supervisor session: failed to replay pending relay to superseding session"); + let sessions = self.sessions.lock().unwrap(); + if sessions + .get(sandbox_id) + .is_none_or(|session| session.session_id != session_id) + { break; } + let mut pending = self.pending_relays.lock().unwrap(); + if let Some(pending) = pending.get_mut(&channel_id) + && pending.delivered_session_id.as_deref() != Some(session_id) + { + pending.delivered_session_id = Some(session_id.to_string()); + permit.send(GatewayMessage { + payload: Some(gateway_message::Payload::RelayOpen( + pending.relay_open.clone(), + )), + }); + } } } } @@ -1390,85 +1467,89 @@ pub async fn open_routed_relay_with_message( ), Status, > { - let deadline = Instant::now() + session_wait_timeout; - let mut backoff = SESSION_WAIT_INITIAL_BACKOFF; - let owner_index = SupervisorOwnerIndex::new(state.store.clone(), OWNER_TTL); - loop { - if state.supervisor_sessions.has_session(sandbox_id) { - match state - .supervisor_sessions - .open_relay_with_message(sandbox_id, relay_open.clone(), Duration::ZERO) - .await - { - Ok(relay) => return Ok(relay), - Err(status) if status.code() == tonic::Code::Unavailable => { - // The session can migrate after `has_session` but before - // RelayOpen reaches its sender. Fall through and reread the - // persisted owner instead of surfacing a handoff race. - warn!( - sandbox_id, - error = %status, - "local supervisor relay disappeared during open; resolving owner again" - ); + let deadline = tokio::time::Instant::now() + session_wait_timeout; + tokio::time::timeout_at(deadline, async { + let mut backoff = SESSION_WAIT_INITIAL_BACKOFF; + let owner_index = SupervisorOwnerIndex::new(state.store.clone(), OWNER_TTL); + loop { + if state.supervisor_sessions.has_session(sandbox_id) { + match state + .supervisor_sessions + .open_relay_with_message_until(sandbox_id, relay_open.clone(), deadline, false) + .await + { + Ok(relay) => return Ok(relay), + Err(status) if status.code() == tonic::Code::Unavailable => { + // The session can migrate after `has_session` but before + // RelayOpen reaches its sender. Fall through and reread the + // persisted owner instead of surfacing a handoff race. + warn!( + sandbox_id, + error = %status, + "local supervisor relay disappeared during open; resolving owner again" + ); + } + Err(status) => return Err(status), } - Err(status) => return Err(status), } - } - if let Some(owner) = resolve_owner(state, &owner_index, sandbox_id).await? - && owner_is_fresh(&owner) - { - if owner.owner_replica_id == state.replica_id { - warn!( - sandbox_id, - owner_replica_id = %owner.owner_replica_id, - "supervisor owner record points at this replica but no local session is registered; retrying" - ); - state.peer_routes.evict_owner(sandbox_id); - if Instant::now() + backoff > deadline { - return Err(Status::unavailable("supervisor session not connected")); - } - tokio::time::sleep(backoff).await; - backoff = (backoff * 2).min(SESSION_WAIT_MAX_BACKOFF); - continue; - } - if owner_endpoint_is_local_only(&owner.owner_peer_endpoint) { - return Err(Status::failed_precondition(format!( - "sandbox is owned by gateway replica {} which advertises no peer endpoint; \ - set OPENSHELL_PEER_ENDPOINT on every replica to route across replicas", - owner.owner_replica_id - ))); - } - match open_peer_relay( - state, - owner.owner_peer_endpoint.clone(), - sandbox_id, - relay_open.clone(), - ) - .await + if let Some(owner) = resolve_owner(state, &owner_index, sandbox_id).await? + && owner_is_fresh(&owner) { - Ok(relay) => return Ok(relay), - Err(status) => { + if owner.owner_replica_id == state.replica_id { warn!( sandbox_id, owner_replica_id = %owner.owner_replica_id, - owner_peer_endpoint = %owner.owner_peer_endpoint, - error = %status, - "gateway peer owner relay open failed; retrying until session wait timeout" + "supervisor owner record points at this replica but no local session is registered; retrying" ); - // The record may name a replaced pod, so retry against a - // fresh read rather than the cached endpoint. state.peer_routes.evict_owner(sandbox_id); + if tokio::time::Instant::now() + backoff > deadline { + return Err(Status::unavailable("supervisor session not connected")); + } + tokio::time::sleep(backoff).await; + backoff = (backoff * 2).min(SESSION_WAIT_MAX_BACKOFF); + continue; + } + if owner_endpoint_is_local_only(&owner.owner_peer_endpoint) { + return Err(Status::failed_precondition(format!( + "sandbox is owned by gateway replica {} which advertises no peer endpoint; \ + set OPENSHELL_PEER_ENDPOINT on every replica to route across replicas", + owner.owner_replica_id + ))); + } + match open_peer_relay( + state, + owner.owner_peer_endpoint.clone(), + sandbox_id, + relay_open.clone(), + ) + .await + { + Ok(relay) => return Ok(relay), + Err(status) => { + warn!( + sandbox_id, + owner_replica_id = %owner.owner_replica_id, + owner_peer_endpoint = %owner.owner_peer_endpoint, + error = %status, + "gateway peer owner relay open failed; retrying until session wait timeout" + ); + // The record may name a replaced pod, so retry against a + // fresh read rather than the cached endpoint. + state.peer_routes.evict_owner(sandbox_id); + } } } - } - if Instant::now() + backoff > deadline { - return Err(Status::unavailable("supervisor session not connected")); + if tokio::time::Instant::now() + backoff > deadline { + return Err(Status::unavailable("supervisor session not connected")); + } + tokio::time::sleep(backoff).await; + backoff = (backoff * 2).min(SESSION_WAIT_MAX_BACKOFF); } - tokio::time::sleep(backoff).await; - backoff = (backoff * 2).min(SESSION_WAIT_MAX_BACKOFF); - } + }) + .await + .map_err(|_| Status::deadline_exceeded("supervisor relay setup timed out"))? } /// Reads the owning replica, reusing a recent result when one is cached. @@ -1960,12 +2041,13 @@ async fn establish_supervisor_session( } state.telemetry.sandbox_session_connected(&sandbox_id); - if superseded { - state - .supervisor_sessions - .replay_pending_relays(&sandbox_id, &tx) - .await; - } + // A disconnected session may already have removed its registration while + // an unclaimed RelayOpen remains pending. Replay on every accepted session, + // including reconnects that did not supersede a live registration. + state + .supervisor_sessions + .replay_pending_relays(&sandbox_id, &session_id, &tx) + .await; // Step 4: Spawn the session loop that reads inbound messages. let state_clone = Arc::clone(&state); @@ -2504,6 +2586,7 @@ mod tests { service_id: String::new(), }, created_at, + delivered_session_id: None, } } @@ -2928,6 +3011,200 @@ mod tests { .expect("shutdown signal should arrive at superseded session"); } + #[tokio::test] + async fn local_relay_attempt_skips_missing_session_without_spending_setup_budget() { + let registry = SupervisorSessionRegistry::new(); + let started = tokio::time::Instant::now(); + let attempt = registry.open_relay_with_message_until( + "missing", + RelayOpen { + channel_id: "test-channel".into(), + ..Default::default() + }, + started + Duration::from_secs(15), + false, + ); + tokio::pin!(attempt); + let std::task::Poll::Ready(Err(error)) = futures_util::poll!(&mut attempt) else { + panic!("local routing must immediately fall back when its session disappears"); + }; + assert_eq!(error.code(), tonic::Code::Unavailable); + assert!(registry.pending_relays.lock().unwrap().is_empty()); + } + + #[tokio::test(start_paused = true)] + async fn relay_setup_deadline_bounds_queue_wait_for_nonwaiting_local_attempt() { + let registry = SupervisorSessionRegistry::new(); + let (tx, mut rx) = mpsc::channel(1); + registry.register("sbx".into(), "session".into(), tx.clone(), make_shutdown()); + tx.send(GatewayMessage::default()).await.unwrap(); + let deadline = tokio::time::Instant::now() + Duration::from_secs(15); + let attempt = registry.open_relay_with_message_until( + "sbx", + RelayOpen { + channel_id: "test-channel".into(), + ..Default::default() + }, + deadline, + false, + ); + tokio::pin!(attempt); + // Skipping a missing-session wait must not imply a zero queue budget. + assert!(futures_util::poll!(&mut attempt).is_pending()); + tokio::time::advance(Duration::from_secs(15)).await; + assert_eq!( + attempt.await.unwrap_err().code(), + tonic::Code::DeadlineExceeded + ); + assert!(registry.pending_relays.lock().unwrap().is_empty()); + rx.recv().await.unwrap(); + assert!(matches!( + rx.try_recv(), + Err(mpsc::error::TryRecvError::Empty) + )); + } + + #[tokio::test(start_paused = true)] + async fn relay_setup_deadline_is_not_reset_by_reconnects() { + let registry = SupervisorSessionRegistry::new(); + let (tx, mut rx) = mpsc::channel(1); + registry.register( + "sbx".into(), + "session-0".into(), + tx.clone(), + make_shutdown(), + ); + tx.send(GatewayMessage::default()).await.unwrap(); + let started = tokio::time::Instant::now(); + let attempt = registry.open_relay("sbx", Duration::from_secs(9)); + tokio::pin!(attempt); + assert!(futures_util::poll!(&mut attempt).is_pending()); + for generation in 1..=2 { + tokio::time::advance(Duration::from_secs(3)).await; + let (new_tx, new_rx) = mpsc::channel(1); + new_tx.send(GatewayMessage::default()).await.unwrap(); + registry.register( + "sbx".into(), + format!("session-{generation}"), + new_tx, + make_shutdown(), + ); + rx.recv().await.unwrap(); + assert!(futures_util::poll!(&mut attempt).is_pending()); + rx = new_rx; + } + tokio::time::advance(Duration::from_secs(3)).await; + assert_eq!( + attempt.await.unwrap_err().code(), + tonic::Code::DeadlineExceeded + ); + assert_eq!(started.elapsed(), Duration::from_secs(9)); + assert!(registry.pending_relays.lock().unwrap().is_empty()); + } + + #[tokio::test] + async fn replay_pending_relays_does_not_duplicate_forward_opened_after_registration() { + let registry = SupervisorSessionRegistry::new(); + let (tx_old, mut rx_old) = mpsc::channel(4); + registry.register("sbx".into(), "old".into(), tx_old, make_shutdown()); + let (old_channel, _old_relay_rx) = registry + .open_relay("sbx", Duration::from_secs(1)) + .await + .unwrap(); + rx_old.recv().await.unwrap(); + registry.remove_if_current("sbx", "old"); + + // Session establishment is paused between registration and replay. + let (tx_new, mut rx_new) = mpsc::channel(4); + registry.register("sbx".into(), "new".into(), tx_new.clone(), make_shutdown()); + let (new_channel, new_relay_rx) = registry + .open_relay("sbx", Duration::from_secs(1)) + .await + .unwrap(); + registry.replay_pending_relays("sbx", "new", &tx_new).await; + registry.replay_pending_relays("sbx", "new", &tx_new).await; + + for expected in [new_channel.clone(), old_channel] { + let Some(gateway_message::Payload::RelayOpen(open)) = + rx_new.recv().await.unwrap().payload + else { + panic!("expected RelayOpen"); + }; + assert_eq!(open.channel_id, expected); + } + assert!(matches!( + rx_new.try_recv(), + Err(mpsc::error::TryRecvError::Empty) + )); + let principal = sandbox_principal("sbx"); + let _claimed = registry + .claim_relay(&new_channel, Some(&principal)) + .unwrap(); + assert!(new_relay_rx.await.unwrap().is_ok()); + assert_eq!( + registry + .claim_relay(&new_channel, Some(&principal)) + .unwrap_err() + .code(), + tonic::Code::NotFound + ); + } + + #[tokio::test] + async fn open_relay_rechecks_session_after_waiting_for_queue_capacity() { + let registry = SupervisorSessionRegistry::new(); + let (tx_old, mut rx_old) = mpsc::channel(1); + registry.register("sbx".into(), "old".into(), tx_old.clone(), make_shutdown()); + tx_old.send(GatewayMessage::default()).await.unwrap(); + let open = registry.open_relay("sbx", Duration::from_secs(1)); + tokio::pin!(open); + assert!(futures_util::poll!(&mut open).is_pending()); + + let (tx_new, mut rx_new) = mpsc::channel(4); + registry.register("sbx".into(), "new".into(), tx_new.clone(), make_shutdown()); + registry.replay_pending_relays("sbx", "new", &tx_new).await; + rx_old.recv().await.unwrap(); + let (channel_id, _relay_rx) = open.await.unwrap(); + registry.replay_pending_relays("sbx", "new", &tx_new).await; + let Some(gateway_message::Payload::RelayOpen(message)) = + rx_new.recv().await.unwrap().payload + else { + panic!("expected RelayOpen on replacement session"); + }; + assert_eq!(message.channel_id, channel_id); + assert!(rx_old.try_recv().is_err()); + assert!(matches!( + rx_new.try_recv(), + Err(mpsc::error::TryRecvError::Empty) + )); + } + + #[tokio::test] + async fn replay_pending_relays_after_disconnected_session_was_removed() { + let registry = SupervisorSessionRegistry::new(); + let (tx_old, mut rx_old) = mpsc::channel(4); + registry.register("sbx".into(), "old".into(), tx_old, make_shutdown()); + let (channel_id, relay_rx) = registry + .open_relay("sbx", Duration::from_secs(1)) + .await + .unwrap(); + rx_old.recv().await.unwrap(); + registry.remove_if_current("sbx", "old"); + + let (tx_new, mut rx_new) = mpsc::channel(4); + assert!(!registry.register("sbx".into(), "new".into(), tx_new.clone(), make_shutdown())); + registry.replay_pending_relays("sbx", "new", &tx_new).await; + let replayed = rx_new.recv().await.unwrap(); + let Some(gateway_message::Payload::RelayOpen(open)) = replayed.payload else { + panic!("expected replayed RelayOpen"); + }; + assert_eq!(open.channel_id, channel_id); + let _claimed = registry + .claim_relay(&channel_id, Some(&sandbox_principal("sbx"))) + .unwrap(); + assert!(relay_rx.await.unwrap().is_ok()); + } + #[tokio::test] async fn replay_pending_relays_reissues_open_to_superseding_session() { let registry = SupervisorSessionRegistry::new(); @@ -2964,7 +3241,7 @@ mod tests { assert!(superseded); registry - .replay_pending_relays("sbx", ®istry.lookup_session("sbx").unwrap()) + .replay_pending_relays("sbx", "s-new", ®istry.lookup_session("sbx").unwrap()) .await; let replayed = rx_new From fde79f1aa677a5ffb6f346133d5203324490c6d7 Mon Sep 17 00:00:00 2001 From: Simon Scatton <44714756+SDAChess@users.noreply.github.com> Date: Thu, 1 Oct 2026 13:01:20 +0000 Subject: [PATCH 18/33] fix(ci): align integration inputs with release candidate source (#4048) Signed-off-by: Simon Scatton --- .github/workflows/integration-test.yml | 6 ++++++ .github/workflows/prepare-integration-inputs.yml | 12 ++++++++++-- .github/workflows/release-tag.yml | 1 + CI.md | 7 +++++-- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index bc91802882..16141e6946 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -11,6 +11,11 @@ on: required: false type: string default: "" + source-sha: + description: Candidate source revision; defaults to the artifact run head SHA + required: false + type: string + default: "" category: description: Integration-test category, used for the job name and concurrency key required: true @@ -38,6 +43,7 @@ jobs: uses: ./.github/workflows/prepare-integration-inputs.yml with: artifact-run-id: ${{ inputs['artifact-run-id'] }} + source-sha: ${{ inputs['source-sha'] }} integration: needs: prepare diff --git a/.github/workflows/prepare-integration-inputs.yml b/.github/workflows/prepare-integration-inputs.yml index 77be3181d0..d80e4943a1 100644 --- a/.github/workflows/prepare-integration-inputs.yml +++ b/.github/workflows/prepare-integration-inputs.yml @@ -11,6 +11,11 @@ on: required: false type: string default: "" + source-sha: + description: Candidate source revision; defaults to the artifact run head SHA + required: false + type: string + default: "" deb-artifact-name: description: Debian package artifact to include in the tmachine inputs required: false @@ -43,9 +48,12 @@ jobs: env: ARTIFACT_RUN_ID: ${{ inputs['artifact-run-id'] || github.run_id }} GH_TOKEN: ${{ github.token }} + SOURCE_SHA: ${{ inputs['source-sha'] }} run: | - head_sha=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${ARTIFACT_RUN_ID}" --jq .head_sha) - echo "source_sha=${head_sha}" >> "$GITHUB_OUTPUT" + if [ -z "${SOURCE_SHA}" ]; then + SOURCE_SHA=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${ARTIFACT_RUN_ID}" --jq .head_sha) + fi + echo "source_sha=${SOURCE_SHA}" >> "$GITHUB_OUTPUT" - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 59c2a542b4..3e18a0936f 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -159,6 +159,7 @@ jobs: packages: read uses: ./.github/workflows/prepare-integration-inputs.yml with: + source-sha: ${{ needs.compute-versions.outputs.source_sha }} deb-artifact-name: deb-linux-amd64 conformance-integration: diff --git a/CI.md b/CI.md index 24bee35696..6c07eac087 100644 --- a/CI.md +++ b/CI.md @@ -49,8 +49,11 @@ its v1alpha1 fallback, so v0.5.0 is not the overall minimum supported version. ### Run only the policy advisor conformance tests Manually dispatch `Integration Tests` on the candidate branch with an -`artifact-run-id` from a build of the same commit. Set `category` to -`policy-advisor` and `test-matrix` to: +`artifact-run-id` from a build of the same commit. When a `Release Tag` run was +dispatched from a different commit, also set `source-sha` to the release tag's +resolved commit so runtime images and test inputs match the candidate binaries. +Otherwise, `source-sha` defaults to the artifact run's head SHA. Set `category` +to `policy-advisor` and `test-matrix` to: ```json [{"environment":"ubuntu-docker-rootful","installer":"binaries","testsuite":"policy-advisor"}] From 0e8d9e55f987661e63fc62acfc859fa818b1c6ef Mon Sep 17 00:00:00 2001 From: Evan Lezar Date: Thu, 1 Oct 2026 13:17:20 +0000 Subject: [PATCH 19/33] test(e2e): remove schema parity campaign (#3864) Signed-off-by: Evan Lezar --- .../build-openshell-mxc-windows/SKILL.md | 4 +- AGENTS.md | 2 +- TESTING.md | 7 +- crates/openshell-gateway/src/lib.rs | 6 - .../gateway/schema-v2-capability-parity.toml | 400 --------- ...schema-v2-compute-boundary-comparison.json | 376 -------- .../schema-v2-cross-cutting-dispositions.toml | 109 --- .../schema-v2-intentional-changes.toml | 170 ---- .../schema-v2-kubernetes-core-comparison.json | 9 - ...ema-v2-kubernetes-option-dispositions.toml | 152 ---- .../gateway/schema-v2-live-results.toml | 355 -------- .../schema-v2-parity-gap-dispositions.toml | 106 --- .../schema-v2-step11-baseline-attestation.txt | 101 --- ...schema-v2-step11-candidate-attestation.txt | 102 --- e2e/parity/gateway-options.sh | 224 ----- e2e/parity/kubernetes-options-test.sh | 54 -- e2e/parity/kubernetes-options.sh | 432 --------- e2e/parity/podman-options.sh | 111 --- e2e/parity/run.sh | 658 -------------- e2e/parity/test.sh | 593 ------------- e2e/parity/trace-cli.sh | 31 - e2e/parity/trace-conformance.sh | 37 - e2e/parity/verify-results.py | 839 ------------------ e2e/support/debian-package-manifest.py | 43 - e2e/support/podman-gateway-config.sh | 237 ++--- e2e/with-podman-gateway.sh | 324 +------ ...ateway_schema_v2_capability_parity_test.py | 293 ------ ...chema_v2_compute_boundary_verifier_test.py | 489 ---------- ...hema_v2_cross_cutting_dispositions_test.py | 173 ---- ...eway_schema_v2_intentional_changes_test.py | 158 ---- ..._v2_kubernetes_option_dispositions_test.py | 139 --- .../gateway_schema_v2_live_results_test.py | 449 ---------- ..._schema_v2_parity_gap_dispositions_test.py | 195 ---- tasks/parity.toml | 33 - tasks/scripts/test-gateway-config.sh | 43 + tasks/test.toml | 1 - 36 files changed, 138 insertions(+), 7317 deletions(-) delete mode 100644 e2e/configs/gateway/schema-v2-capability-parity.toml delete mode 100644 e2e/configs/gateway/schema-v2-compute-boundary-comparison.json delete mode 100644 e2e/configs/gateway/schema-v2-cross-cutting-dispositions.toml delete mode 100644 e2e/configs/gateway/schema-v2-intentional-changes.toml delete mode 100644 e2e/configs/gateway/schema-v2-kubernetes-core-comparison.json delete mode 100644 e2e/configs/gateway/schema-v2-kubernetes-option-dispositions.toml delete mode 100644 e2e/configs/gateway/schema-v2-live-results.toml delete mode 100644 e2e/configs/gateway/schema-v2-parity-gap-dispositions.toml delete mode 100644 e2e/configs/gateway/schema-v2-step11-baseline-attestation.txt delete mode 100644 e2e/configs/gateway/schema-v2-step11-candidate-attestation.txt delete mode 100755 e2e/parity/gateway-options.sh delete mode 100644 e2e/parity/kubernetes-options-test.sh delete mode 100644 e2e/parity/kubernetes-options.sh delete mode 100755 e2e/parity/podman-options.sh delete mode 100755 e2e/parity/run.sh delete mode 100755 e2e/parity/test.sh delete mode 100755 e2e/parity/trace-cli.sh delete mode 100755 e2e/parity/trace-conformance.sh delete mode 100644 e2e/parity/verify-results.py delete mode 100644 e2e/support/debian-package-manifest.py delete mode 100644 python/openshell/gateway_schema_v2_capability_parity_test.py delete mode 100644 python/openshell/gateway_schema_v2_compute_boundary_verifier_test.py delete mode 100644 python/openshell/gateway_schema_v2_cross_cutting_dispositions_test.py delete mode 100644 python/openshell/gateway_schema_v2_intentional_changes_test.py delete mode 100644 python/openshell/gateway_schema_v2_kubernetes_option_dispositions_test.py delete mode 100644 python/openshell/gateway_schema_v2_live_results_test.py delete mode 100644 python/openshell/gateway_schema_v2_parity_gap_dispositions_test.py delete mode 100644 tasks/parity.toml diff --git a/.agents/skills/build-openshell-mxc-windows/SKILL.md b/.agents/skills/build-openshell-mxc-windows/SKILL.md index 8b1bcc0060..a6f71388ff 100644 --- a/.agents/skills/build-openshell-mxc-windows/SKILL.md +++ b/.agents/skills/build-openshell-mxc-windows/SKILL.md @@ -215,8 +215,8 @@ compatibility under emulation is not part of these tasks. The aggregate commands above on an ARM64 host. The repository-wide `mise run pre-commit` task is also supported on Windows. -Run `rust:lockfiles:check`, `sdk:ts:ci`, `go:ci`, and `test:e2e-parity` through -the Windows-aware tasks when validating those surfaces. Do not count the Go +Run `rust:lockfiles:check`, `sdk:ts:ci`, and `go:ci` through the Windows-aware +tasks when validating those surfaces. Do not count the Go Windows ARM64 race-detector exclusion or POSIX permission-bit skips as security coverage. SDK test dependencies must remain at their lockfile versions. Its Rust check, Clippy, and test dependencies enter the same MSVC environment diff --git a/AGENTS.md b/AGENTS.md index a590b117ce..2f14af0a16 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,7 +36,7 @@ These pipelines connect skills into end-to-end workflows. Individual skill files |------|-----------|---------| | `crates/openshell-cli/` | CLI binary | User-facing command-line interface | | `crates/openshell-conformance/` | CLI conformance library | Reusable driver-agnostic scenarios and command runner | -| `crates/openshell-conformance-cli/` | Conformance CLI | Distributable `list` and `run` entrypoint for gateway conformance | +| `crates/openshell-conformance-cli/` | Conformance CLI | Legacy local `list` and `run` entrypoint pending follow-up cleanup | | `crates/openshell-server/` | Gateway server | Control-plane API, sandbox lifecycle, auth boundary | | `crates/openshell-sandbox/` | Sandbox runtime | Capability-free workload launcher, process identity, and seccomp-mediated I/O | | `crates/openshell-supervisor/` | Supervisor runtime | Gateway session, policy evaluation, credentials, and upstream networking | diff --git a/TESTING.md b/TESTING.md index bce0f36a62..a1c2f9476e 100644 --- a/TESTING.md +++ b/TESTING.md @@ -52,8 +52,7 @@ Rust validation checks tracked Cargo lockfiles; run `mise run rust:lockfiles:che Use `mise run --skip-tools pre-commit` with the existing Rust/MSVC toolchain. Windows now checks tracked Cargo lockfiles through PowerShell rather than -skipping them. The deterministic gateway parity task uses Git for Windows Bash, -with temporary Python launchers confined to a unique checkout-owned directory. +skipping them. `mise run --skip-tools sdk:ts:ci` selects the x64 Biome executable on Windows (including ARM64 hosts running it under emulation), resolves the protobuf @@ -104,6 +103,10 @@ OPENSHELL_GATEWAY_ENDPOINT=http://127.0.0.1:18080 mise run e2e Raw endpoint mode is HTTP-only. Use a named gateway config when a gateway requires mTLS. +`mise run test:gateway-config` validates generated gateway TOML without a live +runtime. It covers the current schema and the Podman in-tree versus external +driver configuration boundary. + ### Python E2E (`e2e/python/`) `mise run e2e:python` builds `openshell/e2e-python:dev` from diff --git a/crates/openshell-gateway/src/lib.rs b/crates/openshell-gateway/src/lib.rs index 376e80380b..dc0ded3f0b 100644 --- a/crates/openshell-gateway/src/lib.rs +++ b/crates/openshell-gateway/src/lib.rs @@ -367,12 +367,6 @@ fn podman_config( if let Ok(path) = std::env::var("OPENSHELL_PODMAN_SOCKET") { config.socket_path = Some(path.into()); } - if let Ok(ip) = std::env::var("OPENSHELL_PODMAN_HOST_GATEWAY_IP") { - config.host_gateway_ip = ip; - } - if let Ok(mode) = std::env::var("OPENSHELL_PODMAN_USERNS") { - config.userns = Some(mode); - } Ok(config) } diff --git a/e2e/configs/gateway/schema-v2-capability-parity.toml b/e2e/configs/gateway/schema-v2-capability-parity.toml deleted file mode 100644 index fd64b60b68..0000000000 --- a/e2e/configs/gateway/schema-v2-capability-parity.toml +++ /dev/null @@ -1,400 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Inventory for the schema-v2 live capability-parity campaign. Every entry is a -# test case contract, not a result. `status = "not_run"` deliberately means no -# live assertion has been made. Keep the oracle observable and put the runtime -# prerequisites in `required_environment`; this lets later waves select a lane -# without rediscovering the migration's intended behavior. -manifest_version = 1 -baseline_ref = "origin/main" -baseline_commit = "74960ebfaeec4673885089ed995fad902459749f" -baseline_schema_version = 1 -candidate_ref = "HEAD" -candidate_commit = "8c868e430e9cd3284d7e274628419ab484ebcee0" -candidate_schema_version = 2 - -# Allowed lane names: deterministic, e2e-docker, e2e-podman, e2e-kubernetes, -# e2e-vm, windows-mxc, extension-driver, auth-oidc, observability, packaging. -# Allowed statuses: not_run, blocked, planned. A live pass is intentionally not -# a status in this first-wave manifest. - -[[capabilities]] -id = "configuration-source-precedence" -topics = ["configuration_producers"] -origin_main_access_paths = ["--config", "OPENSHELL_GATEWAY_CONFIG", "CLI > OPENSHELL_* > [openshell.gateway] > defaults"] -schema_v2_access_paths = ["--config", "OPENSHELL_GATEWAY_CONFIG", "CLI > OPENSHELL_* > [openshell.gateway] > defaults"] -behavioral_oracle = "A CLI or environment value wins over the corresponding file value; an unset value uses the file value." -required_environment = "none; parse and startup-config construction only" -test_lane = "deterministic" -status = "not_run" - -[[capabilities]] -id = "schema-version-and-strict-layout" -topics = ["configuration_producers"] -origin_main_access_paths = ["[openshell] version = 1", "[openshell.gateway] inherited driver defaults"] -schema_v2_access_paths = ["[openshell] version = 2", "[openshell.gateway] gateway-only fields", "[openshell.drivers.] driver-owned fields"] -behavioral_oracle = "Missing, v1, future, unknown gateway, misplaced selected-driver, unknown selected-driver, and non-table driver values fail before runtime construction; unselected driver tables are validated when selected." -required_environment = "none; TOML parser only" -test_lane = "deterministic" -status = "not_run" - -[[capabilities]] -id = "gateway-identity-and-logging" -topics = ["configuration_producers", "observability"] -origin_main_access_paths = ["--name / OPENSHELL_GATEWAY_NAME", "--log-level / OPENSHELL_LOG_LEVEL", "[openshell.gateway].name", "[openshell.gateway].log_level"] -schema_v2_access_paths = ["[openshell.gateway].name", "[openshell.gateway].log_level", "same CLI and environment variables"] -behavioral_oracle = "The configured name and log filter are retained after v2 file merge and identify emitted gateway telemetry." -required_environment = "gateway process with captured logs" -test_lane = "observability" -status = "not_run" - -[[capabilities]] -id = "primary-health-and-metrics-listeners" -topics = ["listeners"] -origin_main_access_paths = ["--bind-address / OPENSHELL_BIND_ADDRESS", "--port / OPENSHELL_SERVER_PORT", "--health-port / OPENSHELL_HEALTH_PORT", "--metrics-port / OPENSHELL_METRICS_PORT", "[openshell.gateway].{bind_address,health_bind_address,metrics_bind_address}"] -schema_v2_access_paths = ["[openshell.gateway].bind_address", "[openshell.gateway].health_bind_address", "[openshell.gateway].metrics_bind_address", "same CLI and environment variables"] -behavioral_oracle = "The primary multiplexed endpoint and optional health/metrics endpoints bind their configured addresses; a zero auxiliary port disables only that auxiliary listener." -required_environment = "loopback TCP ports" -test_lane = "e2e-docker" -status = "not_run" - -[[capabilities]] -id = "database-url-and-persistence-backends" -topics = ["database"] -origin_main_access_paths = ["--db-url / OPENSHELL_DB_URL", "[openshell.gateway].database_url (rejected)"] -schema_v2_access_paths = ["--db-url / OPENSHELL_DB_URL only", "[openshell.gateway].database_url (rejected)"] -behavioral_oracle = "A SQLite or Postgres URL supplied outside TOML opens the store; a URL embedded in TOML is rejected without exposing credentials." -required_environment = "SQLite temporary directory; Postgres service for backend variant" -test_lane = "deterministic" -status = "not_run" - -[[capabilities]] -id = "ssh-rate-limit-and-policy-posture" -topics = ["listeners"] -origin_main_access_paths = ["[openshell.gateway].ssh_session_ttl_secs", "[openshell.gateway].grpc_rate_limit_{requests,window_seconds}", "[openshell.gateway].policy_validation_failure_mode"] -schema_v2_access_paths = ["same [openshell.gateway] fields"] -behavioral_oracle = "SSH TTL, paired rate-limit behavior, and fail_closed versus retain_last_valid policy posture survive migration with their documented validation rules." -required_environment = "gateway with a controllable clock and policy fixture" -test_lane = "e2e-docker" -status = "not_run" - -[[capabilities]] -id = "sandbox-service-routing" -topics = ["listeners"] -origin_main_access_paths = ["--server-san / OPENSHELL_SERVER_SAN", "--enable-loopback-service-http / OPENSHELL_ENABLE_LOOPBACK_SERVICE_HTTP", "[openshell.gateway].{server_sans,enable_loopback_service_http}"] -schema_v2_access_paths = ["[openshell.gateway].server_sans", "[openshell.gateway].enable_loopback_service_http", "same CLI and environment variables"] -behavioral_oracle = "Wildcard SAN routing and loopback-only plaintext sandbox-service HTTP remain available while gateway APIs stay unavailable on that plaintext route." -required_environment = "TLS certificate with sandbox wildcard SAN and loopback HTTP client" -test_lane = "e2e-docker" -status = "not_run" - -[[capabilities]] -id = "gateway-listener-tls-and-sni" -topics = ["auth_tls_jwt", "listeners"] -origin_main_access_paths = ["--tls-cert / OPENSHELL_TLS_CERT", "--tls-key / OPENSHELL_TLS_KEY", "--tls-client-ca / OPENSHELL_TLS_CLIENT_CA", "[openshell.gateway.tls]"] -schema_v2_access_paths = ["[openshell.gateway.tls].{cert_path,key_path,client_ca_path,external_cert_path,external_key_path,external_server_names}", "same CLI and environment variables for primary bundle"] -behavioral_oracle = "The listener presents the primary or configured SNI certificate, rejects the unsupported require_client_auth file field, and rejects incomplete server TLS bundles. The frozen baseline derives client-certificate requirements from client CA and OIDC presence; the candidate permits bearer-only connections and validates any presented client certificate against the configured CA." -required_environment = "test CA, primary and external certificates, TLS client" -test_lane = "e2e-docker" -status = "not_run" - -[[capabilities]] -id = "plaintext-listener-mode" -topics = ["auth_tls_jwt", "listeners"] -origin_main_access_paths = ["--disable-tls / OPENSHELL_DISABLE_TLS", "[openshell.gateway].disable_tls"] -schema_v2_access_paths = ["[openshell.gateway].disable_tls", "same CLI and environment variable"] -behavioral_oracle = "An explicit plaintext listener starts without a listener TLS table and is usable behind a trusted TLS-terminating proxy." -required_environment = "loopback HTTP client" -test_lane = "e2e-docker" -status = "not_run" - -[[capabilities]] -id = "guest-callback-tls-ownership" -topics = ["auth_tls_jwt", "docker", "podman", "vm"] -origin_main_access_paths = ["[openshell.gateway].{guest_tls_ca,guest_tls_cert,guest_tls_key} inherited by local drivers", "driver-local guest_tls_* overrides"] -schema_v2_access_paths = ["[openshell.gateway].guest_tls_ca injected only into selected Docker, Podman, or VM driver", "driver tables reject guest_tls_*"] -behavioral_oracle = "A TLS-enabled selected local driver receives the gateway CA and authenticates callbacks with its sandbox bearer credential; legacy client certificate fields, misplaced TLS fields, and plaintext-incompatible CA settings fail closed." -required_environment = "test CA and sandbox bearer credential; client certificate bundle for the frozen baseline" -test_lane = "e2e-docker" -status = "not_run" - -[[capabilities]] -id = "oidc-bearer-authentication" -topics = ["auth_tls_jwt"] -origin_main_access_paths = ["--oidc-* / OPENSHELL_OIDC_*", "[openshell.gateway.oidc].{issuer,audience,jwks_ttl_secs,roles_claim,admin_role,user_role,scopes_claim}"] -schema_v2_access_paths = ["same [openshell.gateway.oidc] fields", "same CLI and environment variables"] -behavioral_oracle = "A token from the configured issuer is accepted only with the expected audience, role, and optional scope; invalid JWTs are rejected." -required_environment = "OIDC issuer with JWKS and signed test tokens" -test_lane = "auth-oidc" -status = "not_run" - -[[capabilities]] -id = "mtls-user-authentication" -topics = ["auth_tls_jwt"] -origin_main_access_paths = ["--enable-mtls-auth / OPENSHELL_ENABLE_MTLS_AUTH", "[openshell.gateway.mtls_auth].enabled"] -schema_v2_access_paths = ["[openshell.gateway.mtls_auth].enabled", "same CLI and environment variable"] -behavioral_oracle = "A verified client certificate maps to a user principal only when mTLS user auth is enabled and no stronger auth policy replaces it. The candidate defaults mTLS user auth on when a client CA is configured without OIDC, independently of the compute driver." -required_environment = "local driver, test CA, client certificate" -test_lane = "e2e-docker" -status = "not_run" - -[[capabilities]] -id = "unsafe-unauthenticated-user-mode" -topics = ["auth_tls_jwt"] -origin_main_access_paths = ["[openshell.gateway.auth].allow_unauthenticated_users"] -schema_v2_access_paths = ["same [openshell.gateway.auth].allow_unauthenticated_users"] -behavioral_oracle = "The explicit trusted-development switch admits user requests without a credential while sandbox callbacks retain sandbox authentication." -required_environment = "isolated gateway with no public exposure" -test_lane = "e2e-docker" -status = "not_run" - -[[capabilities]] -id = "gateway-minted-sandbox-jwt" -topics = ["auth_tls_jwt"] -origin_main_access_paths = ["[openshell.gateway.gateway_jwt].{signing_key_path,public_key_path,kid_path,gateway_id,ttl_secs}"] -schema_v2_access_paths = ["same [openshell.gateway.gateway_jwt] fields"] -behavioral_oracle = "The gateway mints sandbox and extension tokens with the configured identity, key ID, audience, and positive or omitted TTL semantics; explicit zero is rejected." -required_environment = "Ed25519 keypair and sandbox callback fixture" -test_lane = "e2e-docker" -status = "not_run" - -[[capabilities]] -id = "otlp-observability" -topics = ["observability"] -origin_main_access_paths = ["[openshell.gateway.otlp].{endpoint,service_name}", "OTEL_* SDK tuning environment"] -schema_v2_access_paths = ["same [openshell.gateway.otlp] fields", "same OTEL_* tuning environment"] -behavioral_oracle = "A configured OTLP/gRPC collector receives gateway and in-tree driver traces with gateway name and selected-driver resource attributes; collector failure does not prevent serving." -required_environment = "loopback OTLP/gRPC collector" -test_lane = "observability" -status = "not_run" - -[[capabilities]] -id = "gateway-interceptor-registration" -topics = ["interceptors", "auth_tls_jwt"] -origin_main_access_paths = ["[[openshell.gateway.interceptors]] including endpoint, TLS CA, audience, ordering, failure, timeout, limits, binding policy, and bindings"] -schema_v2_access_paths = ["same [[openshell.gateway.interceptors]] table and [[openshell.gateway.interceptors.bindings]]"] -behavioral_oracle = "Startup validates Describe metadata and configured binding policy; an allowed interceptor observes or modifies only its selected non-secret unary RPC phases." -required_environment = "test interceptor gRPC service; optional private CA or Unix socket" -test_lane = "extension-driver" -status = "not_run" - -[[capabilities]] -id = "supervisor-middleware-registration" -topics = ["middleware", "auth_tls_jwt"] -origin_main_access_paths = ["[[openshell.supervisor.middleware]] including endpoint, TLS CA, audience, payload limit, timeout, and insecure transport"] -schema_v2_access_paths = ["same [[openshell.supervisor.middleware]] table"] -behavioral_oracle = "Gateway startup validates middleware Describe and policy configuration, applies payload/time limits, and distributes only validated registration data to supervisors." -required_environment = "test supervisor middleware gRPC service and sandbox supervisor" -test_lane = "extension-driver" -status = "not_run" - -[[capabilities]] -id = "provider-profile-sources" -topics = ["inference", "interceptors"] -origin_main_access_paths = ["[openshell.gateway].provider_profile_sources"] -schema_v2_access_paths = ["same [openshell.gateway].provider_profile_sources"] -behavioral_oracle = "Configured builtin, user, and interceptor sources form one ordered validated catalog; duplicate normalized profile IDs or invalid interceptor source selections fail closed." -required_environment = "provider records and profile-capable interceptor fixture" -test_lane = "extension-driver" -status = "not_run" - -[[capabilities]] -id = "inference-control-plane-configuration" -topics = ["inference"] -origin_main_access_paths = ["OpenShell inference configuration RPCs and persisted gateway settings; not a startup TOML field"] -schema_v2_access_paths = ["unchanged OpenShell inference configuration RPCs and persisted gateway settings; not a startup TOML field"] -behavioral_oracle = "A provider and route configured through the control plane resolves the same effective inference bundle after a schema-v2 gateway starts." -required_environment = "gateway, provider fixture, and sandbox supervisor" -test_lane = "e2e-docker" -status = "not_run" - -[[capabilities]] -id = "credential-driver-selection-and-kek" -topics = ["credentials"] -origin_main_access_paths = ["[openshell.gateway].credential_drivers", "[openshell.gateway].default_credential_driver", "[openshell.gateway.credential_storage]"] -schema_v2_access_paths = ["same [openshell.gateway] fields"] -behavioral_oracle = "Omission selects encrypted database storage; an explicit empty or multi-driver selection fails, and KEK path/environment configuration preserves credential confidentiality." -required_environment = "temporary gateway database and KEK fixture" -test_lane = "deterministic" -status = "not_run" - -[[capabilities]] -id = "credential-driver-backend-tables" -topics = ["credentials", "external_drivers"] -origin_main_access_paths = ["[openshell.credential_drivers.kubernetes-secrets]", "[openshell.credential_drivers.vault]", "[openshell.credential_drivers.]"] -schema_v2_access_paths = ["same credential-driver tables, including in_tree or uds transport, socket_path, command, args, and startup_timeout_secs"] -behavioral_oracle = "Built-in and remote credential driver tables validate their transport contract and store/retrieve opaque credential material without inline secrets in TOML." -required_environment = "credential-driver mock over UDS; Kubernetes or Vault for backend variants" -test_lane = "extension-driver" -status = "not_run" - -[[capabilities]] -id = "docker-image-and-callback-configuration" -topics = ["docker"] -origin_main_access_paths = ["[openshell.gateway].{default_image,supervisor_image,host_gateway_ip} inherited by Docker", "[openshell.drivers.docker].{socket_path,default_image,image_pull_policy,sandbox_namespace,grpc_endpoint,supervisor_bin,supervisor_image,network_name,host_gateway_ip,ssh_socket_path}"] -schema_v2_access_paths = ["[openshell.drivers.docker].{socket_path,default_image,image_pull_policy,sandbox_label,grpc_endpoint,supervisor_bin,supervisor_image,network_name,host_gateway_ip,ssh_socket_path}"] -behavioral_oracle = "Docker starts a sandbox using its driver table, maps the canonical sandbox label, honors image policy, and derives or honors a callback endpoint." -required_environment = "Linux Docker daemon, bridge network, sandbox and supervisor images" -test_lane = "e2e-docker" -status = "not_run" - -[[capabilities]] -id = "docker-security-and-provider-configuration" -topics = ["docker", "credentials"] -origin_main_access_paths = ["[openshell.drivers.docker].{sandbox_pids_limit,enable_bind_mounts}", "no origin/main Docker equivalent for proxy, SPIFFE, or AppArmor configuration"] -schema_v2_access_paths = ["[openshell.drivers.docker].{sandbox_pids_limit,enable_bind_mounts,https_proxy,no_proxy,proxy_auth_file,proxy_auth_allow_insecure,proxy_connect_by_hostname,provider_spiffe_workload_api_socket,app_armor_profile}"] -behavioral_oracle = "Docker preserves PID and bind-mount behavior while schema v2 adds fail-closed proxy, SPIFFE, and AppArmor configuration whose material is mounted only into the supervisor." -required_environment = "Linux Docker daemon, proxy fixture, optional SPIFFE Unix socket and AppArmor support" -test_lane = "e2e-docker" -status = "not_run" - -[[capabilities]] -id = "podman-image-and-callback-configuration" -topics = ["podman"] -origin_main_access_paths = ["[openshell.gateway].{default_image,supervisor_image,host_gateway_ip} inherited by Podman", "[openshell.drivers.podman].{socket_path,default_image,image_pull_policy,grpc_endpoint,gateway_port,network_name,host_gateway_ip,stop_timeout_secs,supervisor_image}"] -schema_v2_access_paths = ["same [openshell.drivers.podman] fields; gateway_port is runtime-derived"] -behavioral_oracle = "Podman starts a sandbox with its selected socket, image and policy, derives the callback route, and applies stop timeout without v1 gateway inheritance." -required_environment = "Podman service socket, user bridge network, sandbox and supervisor images" -test_lane = "e2e-podman" -status = "not_run" - -[[capabilities]] -id = "podman-runtime-security-and-health" -topics = ["podman", "credentials"] -origin_main_access_paths = ["[openshell.drivers.podman].{sandbox_ssh_socket_path,sandbox_pids_limit,enable_bind_mounts,provider_spiffe_workload_api_socket,app_armor_profile,health_check_interval_secs,https_proxy,no_proxy,proxy_auth_file,proxy_auth_allow_insecure,proxy_connect_by_hostname,proxy_ca_bundle,userns,uidmap,gidmap}"] -schema_v2_access_paths = ["[openshell.drivers.podman].{ssh_socket_path,sandbox_pids_limit,enable_bind_mounts,provider_spiffe_workload_api_socket,app_armor_profile,health_check_interval_secs,https_proxy,no_proxy,proxy_auth_file,proxy_auth_allow_insecure,proxy_connect_by_hostname,proxy_ca_bundle,userns,uidmap,gidmap}"] -behavioral_oracle = "Podman uses the renamed SSH path and validates PID, health, user namespace mappings, AppArmor, proxy, CA, and SPIFFE contracts before a sandbox can run." -required_environment = "Podman service socket, optional proxy/CA/SPIFFE fixture, rootless and rootful variants" -test_lane = "e2e-podman" -status = "not_run" - -[[capabilities]] -id = "kubernetes-core-placement-and-images" -topics = ["kubernetes"] -origin_main_access_paths = ["[openshell.gateway].{default_image,supervisor_image,client_tls_secret_name,service_account_name,host_gateway_ip,enable_user_namespaces,sa_token_ttl_secs} inherited by Kubernetes", "[openshell.drivers.kubernetes].{namespace,default_image,image_pull_policy,image_pull_secrets,service_account_name,supervisor_image,supervisor_image_pull_policy,grpc_endpoint,ssh_socket_path,client_tls_secret_name,host_gateway_ip,enable_user_namespaces,sa_token_ttl_secs}"] -schema_v2_access_paths = ["same fields exclusively in [openshell.drivers.kubernetes]"] -behavioral_oracle = "The Kubernetes driver applies driver-owned namespace, service account, image, pull policy, callback endpoint, SSH socket, TLS Secret, and token TTL settings. The candidate projects only the gateway CA from the TLS Secret into the separate supervisor Pod and authenticates gateway RPCs with a sandbox bearer credential." -required_environment = "Kubernetes cluster, namespace, service account, image pull secret, and client TLS Secret" -test_lane = "e2e-kubernetes" -status = "not_run" - -[[capabilities]] -id = "kubernetes-workspace-isolation" -topics = ["kubernetes"] -origin_main_access_paths = ["[openshell.drivers.kubernetes].{workspace_mode,gateway_id,operator_namespace_label,operator_namespace_file,workspace_default_storage_size,workspace_storage_class,default_runtime_class_name}"] -schema_v2_access_paths = ["same [openshell.drivers.kubernetes] fields"] -behavioral_oracle = "Shared, managed, and operator workspace modes select or create the expected namespace and workspace storage with configured discovery, class, and runtime class." -required_environment = "Kubernetes cluster with namespaces, PVC provisioning, and optional RuntimeClass" -test_lane = "e2e-kubernetes" -status = "not_run" - -[[capabilities]] -id = "kubernetes-supervisor-topology" -topics = ["kubernetes", "middleware"] -origin_main_access_paths = ["[openshell.drivers.kubernetes].{supervisor_sideload_method,topology}", "[openshell.drivers.kubernetes.sidecar].{proxy_uid,process_binary_aware_network_policy}", "[openshell.drivers.kubernetes.managed_ssh_ingress].{enabled,gateway_namespace,gateway_pod_selector}"] -schema_v2_access_paths = ["same Kubernetes driver subtables and fields"] -behavioral_oracle = "The rendered sandbox Pod matches the selected combined or sidecar supervisor topology, sideload method, ingress selector, and sidecar security posture." -required_environment = "Kubernetes cluster supporting selected image-volume or init-container method" -test_lane = "e2e-kubernetes" -status = "not_run" - -[[capabilities]] -id = "kubernetes-egress-spiffe-and-security" -topics = ["kubernetes", "credentials"] -origin_main_access_paths = ["[openshell.drivers.kubernetes].{https_proxy,no_proxy,proxy_auth_secret_name,proxy_auth_secret_key,proxy_auth_allow_insecure,proxy_connect_by_hostname,app_armor_profile,provider_spiffe_workload_api_socket_path,sandbox_uid,sandbox_gid}"] -schema_v2_access_paths = ["same [openshell.drivers.kubernetes] fields"] -behavioral_oracle = "Kubernetes validates proxy/Secret and sidecar-topology relationships, projects SPIFFE only from an allowed path, and applies valid non-root identity and AppArmor settings." -required_environment = "Kubernetes cluster, proxy credential Secret, optional SPIFFE socket, AppArmor-capable node" -test_lane = "e2e-kubernetes" -status = "not_run" - -[[capabilities]] -id = "vm-launch-and-resource-configuration" -topics = ["vm"] -origin_main_access_paths = ["[openshell.gateway].{default_image,guest_tls_ca,guest_tls_cert,guest_tls_key} inherited by VM", "[openshell.drivers.vm].{grpc_endpoint,state_dir,driver_dir,default_image,bootstrap_image,krun_log_level,vcpus,mem_mib,overlay_disk_mib,sandbox_uid,sandbox_gid}", "standalone openshell-driver-vm --openshell-endpoint / OPENSHELL_GRPC_ENDPOINT"] -schema_v2_access_paths = ["[openshell.drivers.vm].{grpc_endpoint,state_dir,driver_dir,default_image,bootstrap_image,krun_log_level,vcpus,mem_mib,overlay_disk_mib,sandbox_uid,sandbox_gid}", "[openshell.gateway].guest_tls_ca", "standalone openshell-driver-vm --grpc-endpoint / OPENSHELL_GRPC_ENDPOINT"] -behavioral_oracle = "The gateway finds and launches the VM driver from driver_dir, forwards the TOML grpc_endpoint through the schema-appropriate standalone-driver flag, and launches a guest with the selected state, images, resources, and identity." -required_environment = "Linux libkrun/KVM host, VM driver binary, and OCI image" -test_lane = "e2e-vm" -status = "not_run" - -[[capabilities]] -id = "vm-guest-security-and-spiffe" -topics = ["vm", "credentials"] -origin_main_access_paths = ["[openshell.drivers.vm].{sandbox_uid,sandbox_gid}"] -schema_v2_access_paths = ["[openshell.drivers.vm].{sandbox_uid,sandbox_gid,https_proxy,no_proxy,proxy_auth_file,proxy_auth_allow_insecure,proxy_connect_by_hostname,provider_spiffe_workload_api_tcp_endpoint,provider_spiffe_allow_guest_tcp}", "gateway-owned guest_tls_ca"] -behavioral_oracle = "VM validates non-root guest ownership, callback TLS, proxy safety, and requires an explicit opt-in before exposing a guest-reachable SPIFFE TCP endpoint." -required_environment = "Linux libkrun/KVM host, TLS and optional proxy/SPIFFE TCP fixture" -test_lane = "e2e-vm" -status = "not_run" - -[[capabilities]] -id = "mxc-windows-driver-configuration" -topics = ["mxc"] -origin_main_access_paths = ["[openshell.drivers.mxc].{wxc_exec_path,backend,pc_least_privilege,pc_capabilities,default_configuration_id,debug}"] -schema_v2_access_paths = ["same [openshell.drivers.mxc] fields"] -behavioral_oracle = "The Windows gateway selects MXC and passes the configured wxc-exec path, backend, AppContainer capabilities, isolation configuration, and debug flag to MXC." -required_environment = "Windows host with MXC/wxc-exec; mock fixture for deterministic smoke variant" -test_lane = "windows-mxc" -status = "not_run" - -[[capabilities]] -id = "external-compute-driver-socket" -topics = ["external_drivers"] -origin_main_access_paths = ["--drivers / --driver / OPENSHELL_DRIVERS plus --compute-driver-socket / OPENSHELL_COMPUTE_DRIVER_SOCKET", "[openshell.drivers.] remote socket table"] -schema_v2_access_paths = ["--compute-driver / OPENSHELL_COMPUTE_DRIVER plus --compute-driver-socket / OPENSHELL_COMPUTE_DRIVER_SOCKET", "[openshell.drivers.].socket_path"] -behavioral_oracle = "A selected non-reserved external driver connects through its configured Unix socket; legacy plural selector flags are rejected, while one OPENSHELL_DRIVERS environment value remains a deprecated upgrade alias when it does not conflict with canonical selection." -required_environment = "external compute-driver gRPC fixture over Unix socket" -test_lane = "extension-driver" -status = "not_run" - -[[capabilities]] -id = "helm-configuration-producer" -topics = ["configuration_producers", "kubernetes"] -origin_main_access_paths = ["Helm rendered schema-v1 gateway TOML and environment-backed overrides"] -schema_v2_access_paths = ["deploy/helm/openshell/templates/gateway-config.yaml renders [openshell] version = 2 and Kubernetes driver table", "Secret-backed OPENSHELL_DB_URL and certificate inputs"] -behavioral_oracle = "helm template renders schema-v2 TOML, derives the Kubernetes callback endpoint, and keeps database and secret material outside the ConfigMap." -required_environment = "Helm and chart test plugin; Kubernetes cluster only for live install variant" -test_lane = "deterministic" -status = "not_run" - -[[capabilities]] -id = "local-launch-script-producers" -topics = ["configuration_producers", "docker", "podman", "vm", "kubernetes"] -origin_main_access_paths = ["tasks/scripts/gateway{,-docker,-podman,-vm}.sh generated schema-v1 TOML"] -schema_v2_access_paths = ["tasks/scripts/gateway{,-docker,-podman,-vm}.sh generated schema-v2 TOML with per-driver tables"] -behavioral_oracle = "Each local launch script emits parseable v2 TOML with a scalar driver and its canonical driver-owned values before it executes the gateway binary." -required_environment = "shell, uv, and fake gateway executable; no runtime daemon required" -test_lane = "deterministic" -status = "not_run" - -[[capabilities]] -id = "e2e-fixture-producers" -topics = ["configuration_producers", "docker", "podman"] -origin_main_access_paths = ["e2e/configs/gateway/docker.toml and podman.toml schema-v1 fixtures"] -schema_v2_access_paths = ["e2e/configs/gateway/docker.toml and podman.toml schema-v2 fixtures"] -behavioral_oracle = "Docker and Podman E2E fixtures parse as v2, select one scalar driver, and contain canonical renamed policy and callback fields." -required_environment = "none; TOML parser only" -test_lane = "deterministic" -status = "not_run" - -[[capabilities]] -id = "rpm-schema-upgrade" -topics = ["packaging_upgrades", "configuration_producers"] -origin_main_access_paths = ["deploy/rpm/gateway.toml.default version 1", "systemd user config seeded from package default"] -schema_v2_access_paths = ["deploy/rpm/gateway.toml.default version 2", "deploy/rpm/migrate-gateway-config.sh exact-v1 replacement"] -behavioral_oracle = "RPM first start seeds the v2 default and upgrade replaces only an exact package-generated v1 file, preserving edited files, modes, and symlink safety." -required_environment = "temporary filesystem and shell; RPM install test host for package variant" -test_lane = "packaging" -status = "not_run" - -[[capabilities]] -id = "homebrew-debian-and-snap-upgrades" -topics = ["packaging_upgrades"] -origin_main_access_paths = ["Homebrew prefix config and migration helper", "Debian XDG user-service config", "Snap $SNAP_COMMON/gateway.toml"] -schema_v2_access_paths = ["package-managed schema-v2 defaults and documented exact-v1/manual migration paths"] -behavioral_oracle = "Each package resolves its documented config location, starts from v2 defaults, and never overwrites operator-edited legacy configuration during upgrade." -required_environment = "macOS Homebrew, Debian/Ubuntu user-service, and Snap test environments" -test_lane = "packaging" -status = "not_run" diff --git a/e2e/configs/gateway/schema-v2-compute-boundary-comparison.json b/e2e/configs/gateway/schema-v2-compute-boundary-comparison.json deleted file mode 100644 index b29829a786..0000000000 --- a/e2e/configs/gateway/schema-v2-compute-boundary-comparison.json +++ /dev/null @@ -1,376 +0,0 @@ -{ - "manifest_version": 2, - "baseline_commit": "74960ebfaeec4673885089ed995fad902459749f", - "candidate_commit": "4a39da510e4d278a24dd60291149519c9a570b46", - "lane": "local-linux-x86_64-rootless-podman-5.8.2", - "retained_evidence_bundles": { - "in_tree": "target/parity/step10-intree-4a39da51", - "external_uds": "target/parity/step10-external-4a39da51" - }, - "oracle": { - "status": true, - "create": true, - "ready": true, - "list_visible": true, - "callback_exec_exact_marker": true, - "delete": true, - "list_empty": true - }, - "in_tree": { - "baseline": { - "schema_version": 1, - "source_sha": "74960ebfaeec4673885089ed995fad902459749f", - "gateway_profile": "in-tree", - "gateway_cargo_features": "default", - "artifact_origins": { - "gateway": "built_by_harness", - "cli": "built_by_harness", - "conformance": "built_by_harness", - "external_driver": "not_applicable", - "supervisor": "built_by_harness" - }, - "artifact_sha256": { - "gateway": "264cf3d809bd1d54633bce9251ec1a5540b567b8150640091df85bdfbe61797a", - "cli": "3ad0ec143bf6850af780bf643c303242956d6ef1066d4a9372bc62fef33ada20", - "conformance": "d669f960333f9bcd777f4fac92e5208fa66d1b51578838e5aba9b1f58bc8dc6e", - "supervisor": "ae2e854936a15a5b952187fd4488a746a8a6e31e9f919967e3ead7a80d4b1077", - "supervisor.Dockerfile": "ec8b25f0a674c0c7ae123c978f6857436af987faea43051e67d3a8d4b52831cd", - "cli-trace-wrapper": "0691d47d9eae7e9fe847a58994e170a480c0e61d1d9013193d6ca9c93476777f", - "supervisor.packages.txt": "6966057ffaf1ef0d4617d413dad53646bcd049a4b08f572796392fb0ffbb01b9" - }, - "launch_attestation": { - "schema_version": 1, - "gateway_port": 32893, - "external_compute_driver": false, - "compute_driver_transport": "in_tree", - "external_driver_pull_policy": "missing", - "supervisor_image": "localhost/openshell/supervisor:parity-baseline-74960ebfaeec", - "supervisor_image_id": "b5bcaae227a1d6ea90fd146fc2904d4166725c8eb1b9590bdfd85b14c7b9caef", - "supervisor_image_digest": "sha256:0f11b6ca65ff33c99d03d6ebd8af239ac9edc148f849201a692a06d722853030", - "supervisor_runtime_image": "localhost/openshell/supervisor@sha256:0f11b6ca65ff33c99d03d6ebd8af239ac9edc148f849201a692a06d722853030", - "supervisor_base_image": "alpine:3.22", - "supervisor_base_image_id": "b66e0ce64844f5c6435b0c4bfd965558199ab0f53270846861c979cb1ac29365", - "supervisor_base_image_digest": "sha256:7c8cb692ae09657cbc4a3f3cbd0e8d5a2690ba38386aaaf252dbb060bf5eb2e6", - "supervisor_base_runtime_image": "docker.io/library/alpine@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce", - "supervisor_package_manifest_sha256": "6966057ffaf1ef0d4617d413dad53646bcd049a4b08f572796392fb0ffbb01b9", - "sandbox_image_request": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c", - "sandbox_image_id": "65fa5d3d598a07d385ddbea41bf593be15af306337b76255b40705287cebcbbf", - "sandbox_image_digest": "sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c", - "sandbox_runtime_image": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c", - "sandbox_client_image_alias": "nvcr.io/nvidia/base/ubuntu:24.04", - "sandbox_client_image_alias_id": "65fa5d3d598a07d385ddbea41bf593be15af306337b76255b40705287cebcbbf", - "gateway_sha256_before_execution": "264cf3d809bd1d54633bce9251ec1a5540b567b8150640091df85bdfbe61797a", - "cli_sha256_before_execution": "3ad0ec143bf6850af780bf643c303242956d6ef1066d4a9372bc62fef33ada20", - "conformance_sha256_before_execution": "d669f960333f9bcd777f4fac92e5208fa66d1b51578838e5aba9b1f58bc8dc6e", - "external_driver_sha256_before_execution": "", - "supervisor_sha256_before_execution": "ae2e854936a15a5b952187fd4488a746a8a6e31e9f919967e3ead7a80d4b1077", - "supervisor_dockerfile_sha256_before_execution": "ec8b25f0a674c0c7ae123c978f6857436af987faea43051e67d3a8d4b52831cd", - "cli_trace_wrapper_sha256_before_execution": "0691d47d9eae7e9fe847a58994e170a480c0e61d1d9013193d6ca9c93476777f", - "external_driver_grpc_endpoint": null, - "external_driver_host_gateway_ip": null, - "external_driver_userns": null, - "external_driver_spiffe": false, - "external_driver_proxy": false, - "external_driver_app_armor": false, - "external_driver_environment": null - }, - "raw_evidence_sha256": { - "baseline.json": "d7e7f18607d1eab7fab65b1643febe46499aeaaa00c100b66f92b11101d798a1", - "baseline.launch.json": "a56fe7df2c454137cebcea17bc022adcb9b00f3a2e7403611f61fede62a9b329", - "baseline.log": "e44a39cf19296908e99c58b661bd1c162f0aedd2fafb9c70a1a875d91e712cbe", - "baseline.gateway.toml": "9d11c7c8455308c21354f2a41b546068f06e4605f9127e0156467c321bac550c", - "baseline.exec.stdout": "f3a45a1114b92419a9c4c91364d584de4161ec171a1a9a73dae5b4daca89f8a9" - }, - "artifacts_verified": true, - "raw_output_verified": true, - "success": true - }, - "candidate": { - "schema_version": 2, - "source_sha": "4a39da510e4d278a24dd60291149519c9a570b46", - "gateway_profile": "in-tree", - "gateway_cargo_features": "default", - "artifact_origins": { - "gateway": "built_by_harness", - "cli": "built_by_harness", - "conformance": "built_by_harness", - "external_driver": "not_applicable", - "supervisor": "built_by_harness" - }, - "artifact_sha256": { - "gateway": "2bd42b145f0438f48a579b010537f501e036035b0e22a4ff70e37db733a6e6ff", - "cli": "82827d9068f3e9c75681a804f8fb48274ecf8acb179a46f179fb9fbfe828f69a", - "conformance": "cf6866bdd9755881bf8e2fe0c60a72b41037c6e31725fe43f3328b5c8435cc6d", - "supervisor": "38e1afd251898593619864557ed948ef76370ec0c4618850474a67d1d0508b40", - "supervisor.Dockerfile": "ec8b25f0a674c0c7ae123c978f6857436af987faea43051e67d3a8d4b52831cd", - "cli-trace-wrapper": "0691d47d9eae7e9fe847a58994e170a480c0e61d1d9013193d6ca9c93476777f", - "supervisor.packages.txt": "6966057ffaf1ef0d4617d413dad53646bcd049a4b08f572796392fb0ffbb01b9" - }, - "launch_attestation": { - "schema_version": 2, - "gateway_port": 55987, - "external_compute_driver": false, - "compute_driver_transport": "in_tree", - "external_driver_pull_policy": "if_not_present", - "supervisor_image": "localhost/openshell/supervisor:parity-candidate-4a39da510e4d", - "supervisor_image_id": "489ba15df40c47957b400e54633c250b4322dc37f2798a59717b57c75421330a", - "supervisor_image_digest": "sha256:6de7479f138e88da131741e64c170aa75ae14b4e7b9b49de2d11f51af7d1b6d7", - "supervisor_runtime_image": "localhost/openshell/supervisor@sha256:6de7479f138e88da131741e64c170aa75ae14b4e7b9b49de2d11f51af7d1b6d7", - "supervisor_base_image": "alpine:3.22", - "supervisor_base_image_id": "b66e0ce64844f5c6435b0c4bfd965558199ab0f53270846861c979cb1ac29365", - "supervisor_base_image_digest": "sha256:7c8cb692ae09657cbc4a3f3cbd0e8d5a2690ba38386aaaf252dbb060bf5eb2e6", - "supervisor_base_runtime_image": "docker.io/library/alpine@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce", - "supervisor_package_manifest_sha256": "6966057ffaf1ef0d4617d413dad53646bcd049a4b08f572796392fb0ffbb01b9", - "sandbox_image_request": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c", - "sandbox_image_id": "65fa5d3d598a07d385ddbea41bf593be15af306337b76255b40705287cebcbbf", - "sandbox_image_digest": "sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c", - "sandbox_runtime_image": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c", - "sandbox_client_image_alias": "nvcr.io/nvidia/base/ubuntu:24.04", - "sandbox_client_image_alias_id": "65fa5d3d598a07d385ddbea41bf593be15af306337b76255b40705287cebcbbf", - "gateway_sha256_before_execution": "2bd42b145f0438f48a579b010537f501e036035b0e22a4ff70e37db733a6e6ff", - "cli_sha256_before_execution": "82827d9068f3e9c75681a804f8fb48274ecf8acb179a46f179fb9fbfe828f69a", - "conformance_sha256_before_execution": "cf6866bdd9755881bf8e2fe0c60a72b41037c6e31725fe43f3328b5c8435cc6d", - "external_driver_sha256_before_execution": "", - "supervisor_sha256_before_execution": "38e1afd251898593619864557ed948ef76370ec0c4618850474a67d1d0508b40", - "supervisor_dockerfile_sha256_before_execution": "ec8b25f0a674c0c7ae123c978f6857436af987faea43051e67d3a8d4b52831cd", - "cli_trace_wrapper_sha256_before_execution": "0691d47d9eae7e9fe847a58994e170a480c0e61d1d9013193d6ca9c93476777f", - "external_driver_grpc_endpoint": null, - "external_driver_host_gateway_ip": null, - "external_driver_userns": null, - "external_driver_spiffe": false, - "external_driver_proxy": false, - "external_driver_app_armor": false, - "external_driver_environment": null - }, - "raw_evidence_sha256": { - "candidate.json": "ed7d6334cccb40488291e35e9b7ad4fb45dd47b55b7e27925384d22907bedefd", - "candidate.launch.json": "384ee89066973cacdb43160a39542096bc3d02da051ac0417fefc0be41c2f619", - "candidate.log": "719aaedc019492ccdc2f1e003af6b12af5bf6d2507fc2573135fca529a9f398f", - "candidate.gateway.toml": "504bbea27b56cc630884ba3eb17eb518f7c55cb7886b10ff353cc777e3d08362", - "candidate.exec.stdout": "d903d8a1f84caca8daa2a5a047c3fe90e2501690fba7c0b62e14d3892b5aadc3" - }, - "artifacts_verified": true, - "raw_output_verified": true, - "success": true - }, - "comparison_sha256": "e97312770ed8cbba6325b901885d083c7f82ef979b5b89e4e0ed2bd32478d9fb", - "classification": "pass", - "parity": true, - "accepted": true - }, - "external_uds": { - "baseline": { - "schema_version": 1, - "source_sha": "74960ebfaeec4673885089ed995fad902459749f", - "gateway_profile": "driver-free", - "gateway_cargo_features": "--no-default-features --features telemetry", - "artifact_origins": { - "gateway": "built_by_harness", - "cli": "built_by_harness", - "conformance": "built_by_harness", - "external_driver": "built_by_harness", - "supervisor": "built_by_harness" - }, - "artifact_sha256": { - "gateway": "5cc2f700d93dde5dd09060d9c9190ae44a99440b1399530a2b23941ec4e88f85", - "cli": "3ad0ec143bf6850af780bf643c303242956d6ef1066d4a9372bc62fef33ada20", - "conformance": "d669f960333f9bcd777f4fac92e5208fa66d1b51578838e5aba9b1f58bc8dc6e", - "supervisor": "ae2e854936a15a5b952187fd4488a746a8a6e31e9f919967e3ead7a80d4b1077", - "supervisor.Dockerfile": "ec8b25f0a674c0c7ae123c978f6857436af987faea43051e67d3a8d4b52831cd", - "cli-trace-wrapper": "0691d47d9eae7e9fe847a58994e170a480c0e61d1d9013193d6ca9c93476777f", - "external-driver": "d976be0580ab5a2e006ca9e1bc1556b84fa810481f6e1c2132793f8e38c7194c", - "supervisor.packages.txt": "6966057ffaf1ef0d4617d413dad53646bcd049a4b08f572796392fb0ffbb01b9" - }, - "launch_attestation": { - "schema_version": 1, - "gateway_port": 50871, - "external_compute_driver": true, - "compute_driver_transport": "remote_uds", - "external_driver_pull_policy": "missing", - "supervisor_image": "localhost/openshell/supervisor:parity-baseline-74960ebfaeec", - "supervisor_image_id": "b2e8f8dae82296a54e7500beb0b2b55d1f8d4ac4afe6ed8de5fba4b3b65ba748", - "supervisor_image_digest": "sha256:b576159e1c7ca3258b9428411977a4fa7eddf2d46aa1a1b2238de7a0081c7e60", - "supervisor_runtime_image": "localhost/openshell/supervisor@sha256:b576159e1c7ca3258b9428411977a4fa7eddf2d46aa1a1b2238de7a0081c7e60", - "supervisor_base_image": "alpine:3.22", - "supervisor_base_image_id": "b66e0ce64844f5c6435b0c4bfd965558199ab0f53270846861c979cb1ac29365", - "supervisor_base_image_digest": "sha256:7c8cb692ae09657cbc4a3f3cbd0e8d5a2690ba38386aaaf252dbb060bf5eb2e6", - "supervisor_base_runtime_image": "docker.io/library/alpine@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce", - "supervisor_package_manifest_sha256": "6966057ffaf1ef0d4617d413dad53646bcd049a4b08f572796392fb0ffbb01b9", - "sandbox_image_request": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c", - "sandbox_image_id": "65fa5d3d598a07d385ddbea41bf593be15af306337b76255b40705287cebcbbf", - "sandbox_image_digest": "sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c", - "sandbox_runtime_image": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c", - "sandbox_client_image_alias": "nvcr.io/nvidia/base/ubuntu:24.04", - "sandbox_client_image_alias_id": "65fa5d3d598a07d385ddbea41bf593be15af306337b76255b40705287cebcbbf", - "gateway_sha256_before_execution": "5cc2f700d93dde5dd09060d9c9190ae44a99440b1399530a2b23941ec4e88f85", - "cli_sha256_before_execution": "3ad0ec143bf6850af780bf643c303242956d6ef1066d4a9372bc62fef33ada20", - "conformance_sha256_before_execution": "d669f960333f9bcd777f4fac92e5208fa66d1b51578838e5aba9b1f58bc8dc6e", - "external_driver_sha256_before_execution": "d976be0580ab5a2e006ca9e1bc1556b84fa810481f6e1c2132793f8e38c7194c", - "supervisor_sha256_before_execution": "ae2e854936a15a5b952187fd4488a746a8a6e31e9f919967e3ead7a80d4b1077", - "supervisor_dockerfile_sha256_before_execution": "ec8b25f0a674c0c7ae123c978f6857436af987faea43051e67d3a8d4b52831cd", - "cli_trace_wrapper_sha256_before_execution": "0691d47d9eae7e9fe847a58994e170a480c0e61d1d9013193d6ca9c93476777f", - "external_driver_grpc_endpoint": "https://host.containers.internal:50871", - "external_driver_host_gateway_ip": "host-gateway", - "external_driver_userns": null, - "external_driver_spiffe": false, - "external_driver_proxy": false, - "external_driver_app_armor": false, - "external_driver_environment": { - "OPENSHELL_COMPUTE_DRIVER_SOCKET": "/tmp/openshell-e2e-podman.H8oMaO/compute-driver.sock", - "OPENSHELL_PODMAN_SOCKET": "/tmp/openshell-e2e-podman.H8oMaO/podman/podman.sock", - "OPENSHELL_SANDBOX_IMAGE": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c", - "OPENSHELL_SANDBOX_IMAGE_PULL_POLICY": "missing", - "OPENSHELL_HEALTH_CHECK_INTERVAL_SECS": 10, - "OPENSHELL_GRPC_ENDPOINT": "https://host.containers.internal:50871", - "OPENSHELL_GATEWAY_PORT": 50871, - "OPENSHELL_NETWORK_NAME": "e2e-podman-659291-50871", - "OPENSHELL_STOP_TIMEOUT": 15, - "OPENSHELL_SUPERVISOR_IMAGE": "localhost/openshell/supervisor@sha256:b576159e1c7ca3258b9428411977a4fa7eddf2d46aa1a1b2238de7a0081c7e60", - "OPENSHELL_PODMAN_TLS_CA": { - "path": "/tmp/openshell-e2e-podman.H8oMaO/pki/ca.crt", - "sha256": "1a77771ebd83be7ed988263ea4a8e42d6b2af94c46861693911c4d5bff6a3fdd" - }, - "OPENSHELL_PODMAN_TLS_CERT": { - "path": "/tmp/openshell-e2e-podman.H8oMaO/pki/client/tls.crt", - "sha256": "0e9ad610227c223ebca348599a113e07f46ab0c6ff6f4ea5598e4c15673e25fd" - }, - "OPENSHELL_PODMAN_TLS_KEY": { - "path": "/tmp/openshell-e2e-podman.H8oMaO/pki/client/tls.key", - "sha256": "d5bbf26f73a8fdfe60e16783bb99275bf35c617b1f229b7c48afb0a3edbacbc0" - }, - "OPENSHELL_ENABLE_BIND_MOUNTS": true - } - }, - "raw_evidence_sha256": { - "baseline.json": "2b48dd376fd02d46c93d6ae0831d3a81d7337893794ca97c6559676e1b8302de", - "baseline.launch.json": "babe872dd643f024da5e4d0697a3a946ba21432d16e7b7abb2e343e82459ec79", - "baseline.log": "1718593d49cd26f76b7ce1d3be5cfca7c70fae7c44976aa1485796b52048ab41", - "baseline.gateway.toml": "8d4f2579c58ba140e95dc1151216cf9962ac211272ce26d2f811b6f3a1a659d0", - "baseline.exec.stdout": "f740ac1e211850739407334863f73311e7ff508bb4e507b7d7fcaf8d8dc2de32", - "baseline.driver.log": "877ab38e85532d3b772ec080adc727ef517203b1b553e75c9b6f29ad06e3374a" - }, - "artifacts_verified": true, - "raw_output_verified": true, - "success": true - }, - "candidate": { - "schema_version": 2, - "source_sha": "4a39da510e4d278a24dd60291149519c9a570b46", - "gateway_profile": "driver-free", - "gateway_cargo_features": "--no-default-features --features telemetry", - "artifact_origins": { - "gateway": "built_by_harness", - "cli": "built_by_harness", - "conformance": "built_by_harness", - "external_driver": "built_by_harness", - "supervisor": "built_by_harness" - }, - "artifact_sha256": { - "gateway": "fdefc047c1b675c311b1796db9f1b1a944456fc34b76547efc0352c6a75a7707", - "cli": "82827d9068f3e9c75681a804f8fb48274ecf8acb179a46f179fb9fbfe828f69a", - "conformance": "cf6866bdd9755881bf8e2fe0c60a72b41037c6e31725fe43f3328b5c8435cc6d", - "supervisor": "38e1afd251898593619864557ed948ef76370ec0c4618850474a67d1d0508b40", - "supervisor.Dockerfile": "ec8b25f0a674c0c7ae123c978f6857436af987faea43051e67d3a8d4b52831cd", - "cli-trace-wrapper": "0691d47d9eae7e9fe847a58994e170a480c0e61d1d9013193d6ca9c93476777f", - "external-driver": "9accb17523a33e8fc4df93b3b3dec619f1f5ad6f5ff3f51abb2a8a5718278f8c", - "supervisor.packages.txt": "6966057ffaf1ef0d4617d413dad53646bcd049a4b08f572796392fb0ffbb01b9" - }, - "launch_attestation": { - "schema_version": 2, - "gateway_port": 32901, - "external_compute_driver": true, - "compute_driver_transport": "remote_uds", - "external_driver_pull_policy": "if_not_present", - "supervisor_image": "localhost/openshell/supervisor:parity-candidate-4a39da510e4d", - "supervisor_image_id": "9c7f51faec648947fe1515ec4bcbab52234e4e143a53bd1bf0a358eadd11440e", - "supervisor_image_digest": "sha256:b4cc939fcf3a95ee7cbfa1560acda5ab8c234b041a21d31ac958810e6b748798", - "supervisor_runtime_image": "localhost/openshell/supervisor@sha256:b4cc939fcf3a95ee7cbfa1560acda5ab8c234b041a21d31ac958810e6b748798", - "supervisor_base_image": "alpine:3.22", - "supervisor_base_image_id": "b66e0ce64844f5c6435b0c4bfd965558199ab0f53270846861c979cb1ac29365", - "supervisor_base_image_digest": "sha256:7c8cb692ae09657cbc4a3f3cbd0e8d5a2690ba38386aaaf252dbb060bf5eb2e6", - "supervisor_base_runtime_image": "docker.io/library/alpine@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce", - "supervisor_package_manifest_sha256": "6966057ffaf1ef0d4617d413dad53646bcd049a4b08f572796392fb0ffbb01b9", - "sandbox_image_request": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c", - "sandbox_image_id": "65fa5d3d598a07d385ddbea41bf593be15af306337b76255b40705287cebcbbf", - "sandbox_image_digest": "sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c", - "sandbox_runtime_image": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c", - "sandbox_client_image_alias": "nvcr.io/nvidia/base/ubuntu:24.04", - "sandbox_client_image_alias_id": "65fa5d3d598a07d385ddbea41bf593be15af306337b76255b40705287cebcbbf", - "gateway_sha256_before_execution": "fdefc047c1b675c311b1796db9f1b1a944456fc34b76547efc0352c6a75a7707", - "cli_sha256_before_execution": "82827d9068f3e9c75681a804f8fb48274ecf8acb179a46f179fb9fbfe828f69a", - "conformance_sha256_before_execution": "cf6866bdd9755881bf8e2fe0c60a72b41037c6e31725fe43f3328b5c8435cc6d", - "external_driver_sha256_before_execution": "9accb17523a33e8fc4df93b3b3dec619f1f5ad6f5ff3f51abb2a8a5718278f8c", - "supervisor_sha256_before_execution": "38e1afd251898593619864557ed948ef76370ec0c4618850474a67d1d0508b40", - "supervisor_dockerfile_sha256_before_execution": "ec8b25f0a674c0c7ae123c978f6857436af987faea43051e67d3a8d4b52831cd", - "cli_trace_wrapper_sha256_before_execution": "0691d47d9eae7e9fe847a58994e170a480c0e61d1d9013193d6ca9c93476777f", - "external_driver_grpc_endpoint": "https://host.containers.internal:32901", - "external_driver_host_gateway_ip": "host-gateway", - "external_driver_userns": null, - "external_driver_spiffe": false, - "external_driver_proxy": false, - "external_driver_app_armor": false, - "external_driver_environment": { - "OPENSHELL_COMPUTE_DRIVER_SOCKET": "/tmp/openshell-e2e-podman.M8xsgQ/compute-driver.sock", - "OPENSHELL_PODMAN_SOCKET": "/tmp/openshell-e2e-podman.M8xsgQ/podman/podman.sock", - "OPENSHELL_SANDBOX_IMAGE": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c", - "OPENSHELL_SANDBOX_IMAGE_PULL_POLICY": "if_not_present", - "OPENSHELL_HEALTH_CHECK_INTERVAL_SECS": 10, - "OPENSHELL_GRPC_ENDPOINT": "https://host.containers.internal:32901", - "OPENSHELL_GATEWAY_PORT": 32901, - "OPENSHELL_NETWORK_NAME": "e2e-podman-660516-32901", - "OPENSHELL_STOP_TIMEOUT": 15, - "OPENSHELL_SUPERVISOR_IMAGE": "localhost/openshell/supervisor@sha256:b4cc939fcf3a95ee7cbfa1560acda5ab8c234b041a21d31ac958810e6b748798", - "OPENSHELL_PODMAN_TLS_CA": { - "path": "/tmp/openshell-e2e-podman.M8xsgQ/pki/ca.crt", - "sha256": "90b268e741240a39838b7074b66ffa30576c053f5c8f5659e1e2e3c0e22809df" - }, - "OPENSHELL_PODMAN_TLS_CERT": { - "path": "/tmp/openshell-e2e-podman.M8xsgQ/pki/client/tls.crt", - "sha256": "a89f555a5e53cf155f122bd2c8b58e0856019468cdf1cf80cafb1c832979f40f" - }, - "OPENSHELL_PODMAN_TLS_KEY": { - "path": "/tmp/openshell-e2e-podman.M8xsgQ/pki/client/tls.key", - "sha256": "a53d8aaea929dec1b355a5e6ae45eb5001f9c3845d3d2cbbff9ca5a7e468be06" - }, - "OPENSHELL_ENABLE_BIND_MOUNTS": true - } - }, - "raw_evidence_sha256": { - "candidate.json": "a68a9a3f78a8ba0fbf33c81e9960e705e554e82077fd4e0e8565a3bc8598790b", - "candidate.launch.json": "5be63290c313302ec2fa35cdd55ec641742d2d7009f7c2746842c42072425f1a", - "candidate.log": "d19a44064c8d90b078e4d34d711c599ae424834de433b355805105ae3f272cf2", - "candidate.gateway.toml": "862322125b3bc9438b87b35448e107d87850f4f3cd61c9c36f045560c963385e", - "candidate.exec.stdout": "fc1085830f6824809d3fdec6ecf518d54a7dde92ec4441e134f13abd5a6a6522", - "candidate.driver.log": "0b977d9c2b69a3e9665d457dd88fe231b3da7a55d74d4ace4c1a467e7cf716d1" - }, - "artifacts_verified": true, - "raw_output_verified": true, - "success": true - }, - "comparison_sha256": "6b30e24a1cf0f9b7d8b5a5de206cfbbfb01c19a7921d6a449900af8448abec4c", - "classification": "pass", - "parity": true, - "accepted": true - }, - "callback_listener": { - "in_tree_baseline_exec": true, - "in_tree_candidate_exec": true, - "external_baseline_exec": true, - "external_candidate_exec": true, - "classification": "pass" - }, - "classification": "pass", - "accepted": true, - "verification": { - "retained_artifact_hashes_recomputed": true, - "raw_lifecycle_output_inspected": true, - "authenticated_preflight_verified": true, - "exact_callback_exec_stdout_verified": true, - "external_driver_allowlist_verified": true, - "external_driver_logs_retained": true, - "external_uds_isolation_verified": true, - "digest_pinned_supervisor_runtime_verified": true, - "same_immutable_sandbox_verified": true, - "supervisor_dependency_provenance_matched": true - } -} diff --git a/e2e/configs/gateway/schema-v2-cross-cutting-dispositions.toml b/e2e/configs/gateway/schema-v2-cross-cutting-dispositions.toml deleted file mode 100644 index 813da3bc61..0000000000 --- a/e2e/configs/gateway/schema-v2-cross-cutting-dispositions.toml +++ /dev/null @@ -1,109 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Step 11 dispositions for the cross-cutting gateway capabilities that are not -# exercised by the compute-driver waves. The paired deterministic preflight is -# regression evidence only: it does not replace a candidate-owned oracle that -# drives both gateway processes and observes the same live behavior. -manifest_version = 1 -baseline_commit = "74960ebfaeec4673885089ed995fad902459749f" -candidate_commit = "363d8540830b2ea294d43198daa2b7a283a2face" -overall_status = "platform_blocked" - -[deterministic_preflight] -status = "pass" -baseline_attestation = "e2e/configs/gateway/schema-v2-step11-baseline-attestation.txt" -baseline_attestation_sha256 = "df3c5e1dcb073eb8db0cb28377516523b331d44fc6f5a5fefab2e92273efaf51" -candidate_attestation = "e2e/configs/gateway/schema-v2-step11-candidate-attestation.txt" -candidate_attestation_sha256 = "2562bff73eba8042199597e814b618b12b93994d15e4ae36e902347347d95fed" -commands = [ - "cargo test -q -p openshell-server --lib", - "cargo test -q -p openshell-gateway-interceptors", - "cargo test -q -p openshell-supervisor-middleware", - "cargo test -q -p openshell-otel-test-support", - "cargo test -q -p openshell-server --test multiplex_tls_integration", - "cargo test -q -p openshell-server --test edge_tunnel_auth", -] -evidence = [ - "The frozen baseline passed 1,455 openshell-server library tests with 8 ignored; the candidate passed 1,481 with 8 ignored.", - "Both revisions passed 49 gateway-interceptor tests, 85 supervisor-middleware tests, 5 multiplex TLS integration tests, and 5 edge-tunnel authentication tests.", - "The openshell-otel-test-support crate compiled successfully on both revisions but currently defines no tests; OTLP behavior is exercised by openshell-server unit tests and still requires a collector-backed live lane.", - "Both runs used checkout-local target directories with sccache disabled after a prior ENOSPC event corrupted the shared cache.", -] - -[[capabilities]] -id = "oidc-bearer-authentication" -status = "platform_blocked" -owner = "OpenShell auth and OIDC CI lane" -lane = "linux-oidc-paired-keycloak-jwks" -blocker = "The host has no isolated paired issuer fixture that presents equivalent signed tokens to both exact-source gateways. Existing Keycloak and OIDC suites are candidate-oriented and cannot establish schema-v1/schema-v2 audience, role, scope, expiry, signature, and key-rotation parity without a shared candidate-owned oracle." - -[[capabilities]] -id = "mtls-user-authentication" -status = "platform_blocked" -owner = "OpenShell gateway authentication CI lane" -lane = "linux-mtls-user-principal-paired" -blocker = "The paired TLS integration tests validate handshake policy but do not drive an authenticated user RPC through both gateways or prove certificate-to-principal mapping. A live lane must use isolated client PKI, equivalent schema files, and the same authorization oracle for enabled and disabled mTLS user authentication." - -[[capabilities]] -id = "unsafe-unauthenticated-user-mode" -status = "platform_blocked" -owner = "OpenShell gateway authentication CI lane" -lane = "linux-auth-chain-paired-isolated" -blocker = "No paired live fixture currently proves that the trusted-development switch admits user requests while sandbox callbacks continue to require sandbox credentials. Candidate-only extension examples use this switch for setup, but that is not an authentication-boundary parity test." - -[[capabilities]] -id = "gateway-minted-sandbox-jwt" -status = "platform_blocked" -owner = "OpenShell gateway JWT CI lane" -lane = "linux-gateway-jwt-claims-paired" -blocker = "Step 10 proved authenticated callback connectivity but did not capture and compare token claims. A controlled callback and extension fixture must verify issuer, gateway identity, key ID, audience, subject, token type, and expiry semantics for both gateways; explicit zero versus omitted TTL remains the ledgered gateway-jwt-zero-sentinel-removed intentional change." - -[[capabilities]] -id = "otlp-observability" -status = "platform_blocked" -owner = "OpenShell observability CI lane" -lane = "linux-otlp-grpc-collector-paired" -blocker = "The deterministic exporter tests passed, but no retained paired collector capture proves emitted gateway and in-tree-driver spans, gateway name, service name, selected-driver resource attributes, or continued serving after collector failure. A loopback OTLP/gRPC collector lane must observe those outcomes from both exact-source processes." - -[[capabilities]] -id = "gateway-interceptor-registration" -status = "platform_blocked" -owner = "OpenShell gateway interceptor CI lane" -lane = "linux-gateway-interceptor-paired" -blocker = "The interceptor library suite passed on both revisions, while the governance-interceptor smoke test remains candidate-only. A paired service must compare Describe validation, binding selection, unary request and response mutation, failure policy, timeout and size limits, and secret exclusion against both gateway schema variants." - -[[capabilities]] -id = "supervisor-middleware-registration" -status = "platform_blocked" -owner = "OpenShell supervisor middleware CI lane" -lane = "linux-supervisor-middleware-paired" -blocker = "The middleware library suite passed on both revisions, while the content-guard smoke test remains candidate-only. A paired gateway, sandbox, and middleware fixture must compare Describe negotiation, policy distribution, guarded and unguarded traffic, failure policy, timeout, and payload limits; the field-name normalization remains the ledgered middleware-payload-name-normalized intentional change." - -[[capabilities]] -id = "provider-profile-sources" -status = "platform_blocked" -owner = "OpenShell provider profile CI lane" -lane = "linux-provider-profiles-interceptor-paired" -blocker = "Server tests cover source construction and duplicate rejection, but no shared live interceptor catalog has been queried through both gateways. The assigned lane must compare builtin, user, and interceptor source ordering, normalized profile identities, duplicate rejection, discovery output, and failure handling with isolated persistent state." - -[[capabilities]] -id = "inference-control-plane-configuration" -status = "platform_blocked" -owner = "OpenShell inference E2E lane" -lane = "linux-inference-provider-routing-paired" -blocker = "Existing inference routing tests are candidate-oriented and this host has no assigned paired provider and model-service fixture. The lane must configure providers and routes through each gateway control plane, observe the effective supervisor bundle, and compare model discovery and an inference request without reusing database state." - -[[capabilities]] -id = "credential-driver-selection-and-kek" -status = "platform_blocked" -owner = "OpenShell credential security CI lane" -lane = "linux-credential-kek-paired" -blocker = "Both server suites passed deterministic selection, KEK, and credential tests, but they are revision-local tests rather than one candidate-owned live oracle. A paired lane must start isolated gateways with independent databases and KEKs, compare default encrypted storage and invalid selections, store and resolve opaque credentials, and verify that logs and configuration do not disclose secrets." - -[[capabilities]] -id = "credential-driver-backend-tables" -status = "platform_blocked" -owner = "OpenShell credential driver E2E lane" -lane = "kubernetes-vault-uds-credential-drivers-paired" -blocker = "The host has no assigned disposable Kubernetes Secrets or Vault backend and no retained paired UDS credential-driver execution. Existing backend E2E coverage is candidate-oriented. The assigned lane must compare in-tree and remote transport validation plus opaque store and retrieve behavior using isolated namespaces, Vault state, sockets, databases, and credentials." diff --git a/e2e/configs/gateway/schema-v2-intentional-changes.toml b/e2e/configs/gateway/schema-v2-intentional-changes.toml deleted file mode 100644 index 78e23234b7..0000000000 --- a/e2e/configs/gateway/schema-v2-intentional-changes.toml +++ /dev/null @@ -1,170 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Explicit compatibility exceptions for the schema-v2 parity campaign. These -# entries document behavior that is not expected to match schema v1 byte for -# byte. They do not waive the requirement that the replacement behavior work. -ledger_version = 1 -issue = 2792 -baseline_commit = "74960ebfaeec4673885089ed995fad902459749f" -candidate_start_commit = "8c868e430e9cd3284d7e274628419ab484ebcee0" - -[[intentional_changes]] -id = "schema-version-cutover" -category = "schema_cutover" -origin_main_contract = "Gateway configuration uses schema version 1; omitted versions were accepted in some paths." -schema_v2_contract = "Every loaded gateway configuration must declare [openshell] version = 2; missing, v1, and future versions fail." -migration = "Set version = 2 and apply every field relocation, rename, and type migration in this ledger before restart." -rationale = "An explicit version boundary prevents a partially migrated security-sensitive configuration from being interpreted with mixed ownership rules." -parity_disposition = "intentional_change" -validation_capability_ids = ["schema-version-and-strict-layout", "rpm-schema-upgrade", "homebrew-debian-and-snap-upgrades"] - -[[intentional_changes]] -id = "singular-compute-driver-selector" -category = "cardinality" -origin_main_contract = "Configuration spells the selector compute_drivers as a list even though runtime selection rejects more than one configured driver." -schema_v2_contract = "Configuration uses one optional scalar compute_driver; omission retains built-in auto-detection." -migration = "Replace compute_drivers = [\"name\"] with compute_driver = \"name\"." -rationale = "The schema now represents the existing one-driver runtime invariant instead of implying unsupported multi-driver operation." -parity_disposition = "intentional_change" -validation_capability_ids = ["schema-version-and-strict-layout", "external-compute-driver-socket"] - -[[intentional_changes]] -id = "driver-table-exclusive-ownership" -category = "ownership" -origin_main_contract = "Selected driver tables inherit an allowlisted set of values from [openshell.gateway]." -schema_v2_contract = "Driver-specific values are read only from [openshell.drivers.]; gateway scope contains only gateway-owned values." -migration = "Move every driver option to the selected driver table instead of relying on gateway inheritance." -rationale = "Only one compute driver is active, so inheritance adds ambiguity and can map one gateway key to different backend meanings." -parity_disposition = "intentional_change" -validation_capability_ids = ["schema-version-and-strict-layout", "docker-image-and-callback-configuration", "podman-image-and-callback-configuration", "kubernetes-core-placement-and-images", "vm-launch-and-resource-configuration"] - -[[intentional_changes]] -id = "kubernetes-fields-relocated" -category = "relocation" -origin_main_contract = "Kubernetes namespace, image, client TLS Secret, ServiceAccount, host gateway, user namespace, and token TTL values may be inherited from gateway scope." -schema_v2_contract = "Those values exist exclusively under [openshell.drivers.kubernetes]." -migration = "Move namespace, default_image, supervisor_image, client_tls_secret_name, service_account_name, host_gateway_ip, enable_user_namespaces, and sa_token_ttl_secs into the Kubernetes driver table." -rationale = "Kubernetes-only deployment controls are not gateway-wide concerns." -parity_disposition = "intentional_change" -validation_capability_ids = ["kubernetes-core-placement-and-images"] - -[[intentional_changes]] -id = "docker-sandbox-label-rename" -category = "rename" -origin_main_contract = "Docker identifies OpenShell containers with sandbox_namespace." -schema_v2_contract = "Docker uses sandbox_label." -migration = "Rename [openshell.drivers.docker].sandbox_namespace to sandbox_label." -rationale = "The value is a Docker container label, not a Kubernetes-style namespace." -parity_disposition = "intentional_change" -validation_capability_ids = ["docker-image-and-callback-configuration"] - -[[intentional_changes]] -id = "podman-ssh-socket-rename" -category = "rename" -origin_main_contract = "Podman uses sandbox_ssh_socket_path in gateway TOML." -schema_v2_contract = "Podman uses ssh_socket_path in gateway TOML." -migration = "Rename [openshell.drivers.podman].sandbox_ssh_socket_path to ssh_socket_path." -rationale = "The canonical name now matches the Docker and Kubernetes driver tables." -parity_disposition = "intentional_change" -validation_capability_ids = ["podman-runtime-security-and-health"] - -[[intentional_changes]] -id = "vm-grpc-endpoint-rename" -category = "rename" -origin_main_contract = "The VM gateway TOML callback override is grpc_endpoint, while the spawned standalone driver uses the internal field openshell_endpoint and flag --openshell-endpoint; OPENSHELL_GRPC_ENDPOINT is the environment override." -schema_v2_contract = "The VM gateway TOML callback override remains grpc_endpoint, and the spawned standalone driver now uses the same internal field name plus --grpc-endpoint; OPENSHELL_GRPC_ENDPOINT is unchanged." -migration = "No gateway TOML key changes. Operators invoking openshell-driver-vm directly must replace --openshell-endpoint with --grpc-endpoint." -rationale = "All compute drivers use the same name for the gateway gRPC callback endpoint." -parity_disposition = "intentional_change" -validation_capability_ids = ["vm-launch-and-resource-configuration", "external-compute-driver-socket"] - -[[intentional_changes]] -id = "canonical-image-pull-policy" -category = "type_normalization" -origin_main_contract = "Drivers accept different pull-policy types and spellings, including Podman missing and Kubernetes-style capitalization." -schema_v2_contract = "Gateway TOML uses always, if_not_present, never, and Podman-only newer through the shared typed policy." -migration = "Translate legacy or runtime-native spellings to canonical lowercase values." -rationale = "A shared validated type rejects typos before contacting a container runtime while preserving Podman's additional newer behavior." -parity_disposition = "intentional_change" -validation_capability_ids = ["docker-image-and-callback-configuration", "podman-image-and-callback-configuration", "kubernetes-core-placement-and-images", "local-launch-script-producers"] - -[[intentional_changes]] -id = "gateway-jwt-zero-sentinel-removed" -category = "sentinel_removal" -origin_main_contract = "gateway_jwt.ttl_secs = 0 means that sandbox tokens do not expire." -schema_v2_contract = "Omitting gateway_jwt.ttl_secs means non-expiring sandbox tokens; an explicit zero is invalid." -migration = "Remove ttl_secs when non-expiring local tokens are intended, or set a positive duration." -rationale = "Omission distinguishes a deliberate absence of expiry from an invalid duration." -parity_disposition = "intentional_change" -validation_capability_ids = ["gateway-minted-sandbox-jwt"] - -[[intentional_changes]] -id = "sandbox-pid-zero-sentinel-removed" -category = "sentinel_removal" -origin_main_contract = "Docker and Podman sandbox_pids_limit use zero as a backend-default sentinel." -schema_v2_contract = "Omitting sandbox_pids_limit uses the OpenShell default of 2048; configured values must be positive." -migration = "Remove a zero sandbox_pids_limit or replace it with a positive explicit limit." -rationale = "A typed optional non-zero limit removes ambiguous zero handling and gives both local container drivers one default." -parity_disposition = "intentional_change" -validation_capability_ids = ["docker-security-and-provider-configuration", "podman-runtime-security-and-health"] - -[[intentional_changes]] -id = "podman-pid-limit-restored" -category = "bug_fix" -origin_main_contract = "Podman accepts a positive sandbox_pids_limit, but serializes it with Docker's PidsLimit shape; libpod ignores that field and applies its default limit of 2048." -schema_v2_contract = "Podman serializes a positive sandbox_pids_limit as OCI resource_limits.pids.limit, and the configured value is applied to the container." -migration = "No configuration migration is required; existing positive values begin taking effect after upgrading." -rationale = "Schema-v2 live validation exposed that the frozen baseline accepted this security control without enforcing it at runtime. Preserving that defect would make configuration parity unsafe." -parity_disposition = "intentional_change" -validation_capability_ids = ["podman-runtime-security-and-health"] - -[[intentional_changes]] -id = "podman-health-zero-sentinel-removed" -category = "sentinel_removal" -origin_main_contract = "Podman health_check_interval_secs = 0 disables health checks." -schema_v2_contract = "Omitting health_check_interval_secs disables gateway-managed Podman health checks; configured values must be positive." -migration = "Remove a zero health_check_interval_secs or set a positive interval." -rationale = "Optional non-zero duration expresses enabled and disabled states without a sentinel." -parity_disposition = "intentional_change" -validation_capability_ids = ["podman-runtime-security-and-health"] - -[[intentional_changes]] -id = "guest-tls-centralized" -category = "ownership" -origin_main_contract = "Local driver tables may override guest_tls_ca, guest_tls_cert, and guest_tls_key inherited from gateway scope." -schema_v2_contract = "The gateway CA is gateway-owned and injected into the selected Docker, Podman, or VM driver for supervisor TLS; legacy client certificate fields and driver-local guest TLS fields are rejected." -migration = "Keep guest_tls_ca under [openshell.gateway] only and remove guest_tls_cert and guest_tls_key. Supervisors authenticate gateway RPCs with sandbox bearer credentials." -rationale = "Gateway TLS trust is separate from sandbox identity. Centralized CA validation supplies supervisor trust without exposing a user client certificate or private key." -parity_disposition = "intentional_change" -validation_capability_ids = ["guest-callback-tls-ownership"] - -[[intentional_changes]] -id = "middleware-payload-name-normalized" -category = "rename_with_alias" -origin_main_contract = "Supervisor middleware uses max_body_bytes while gateway interceptors use max_response_bytes." -schema_v2_contract = "Supervisor middleware uses max_payload_bytes and continues accepting max_body_bytes as a compatibility alias." -migration = "Prefer max_payload_bytes in new configuration; existing max_body_bytes remains accepted." -rationale = "Payload describes middleware data more accurately without forcing an immediate compatibility break." -parity_disposition = "intentional_change" -validation_capability_ids = ["supervisor-middleware-registration"] - -[[intentional_changes]] -id = "vm-sandbox-identity-selection" -category = "default_behavior" -origin_main_contract = "New VM sandboxes default to UID and GID 10001 when no explicit identity is configured." -schema_v2_contract = "New VM root filesystems use the image sandbox account when present and otherwise UID/GID 1000; persisted overlays retain recorded or recoverable identity and ambiguous state fails closed." -migration = "Set sandbox_uid and sandbox_gid for a fixed identity, or retain the generated owner marker with persistent VM state." -rationale = "Image-derived identity preserves filesystem ownership, while explicit state evidence avoids silently reassigning legacy overlays." -parity_disposition = "intentional_change" -validation_capability_ids = ["vm-launch-and-resource-configuration", "vm-guest-security-and-spiffe"] - -[[intentional_changes]] -id = "package-default-only-auto-migration" -category = "migration_policy" -origin_main_contract = "Package-managed and operator-edited schema-v1 configuration may exist at upgrade time." -schema_v2_contract = "RPM and Homebrew automatically replace only recognized byte-identical package defaults; Debian and Snap preserve every legacy file and fail closed through read-only package preflight with explicit manual-migration guidance because their historical generated defaults have no safe provenance marker." -migration = "Automatically migrate recognized defaults, preserve every edited file, and require manual schema-v2 conversion when a package cannot prove that a legacy file is an untouched default." -rationale = "An upgrade must not overwrite operator intent merely because the old schema no longer parses." -parity_disposition = "intentional_change" -validation_capability_ids = ["rpm-schema-upgrade", "homebrew-debian-and-snap-upgrades"] diff --git a/e2e/configs/gateway/schema-v2-kubernetes-core-comparison.json b/e2e/configs/gateway/schema-v2-kubernetes-core-comparison.json deleted file mode 100644 index 344343adf1..0000000000 --- a/e2e/configs/gateway/schema-v2-kubernetes-core-comparison.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "accepted": true, - "baseline_commit": "74960ebfaeec4673885089ed995fad902459749f", - "baseline_success": true, - "candidate_commit": "0f08b5822e4da98c9ced3d4b0f2bf4f30dae28fd", - "candidate_success": true, - "classification": "pass", - "parity": true -} diff --git a/e2e/configs/gateway/schema-v2-kubernetes-option-dispositions.toml b/e2e/configs/gateway/schema-v2-kubernetes-option-dispositions.toml deleted file mode 100644 index 75e2f35b03..0000000000 --- a/e2e/configs/gateway/schema-v2-kubernetes-option-dispositions.toml +++ /dev/null @@ -1,152 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Step 8 records field-level Kubernetes evidence separately from the broader -# capability manifest. A core pass means both frozen schema v1 and schema v2 -# successfully produced and executed the same observed Kubernetes resources. -# A platform-blocked field or value still requires its assigned qualifying lane. -manifest_version = 1 -baseline_commit = "74960ebfaeec4673885089ed995fad902459749f" -validated_candidate_commit = "0f08b5822e4da98c9ced3d4b0f2bf4f30dae28fd" -core_comparison = "e2e/configs/gateway/schema-v2-kubernetes-core-comparison.json" - -[[coverage]] -id = "shared-combined-core" -status = "pass" -lane = "kind-v1.36.1-rootless-podman-host-gateway" -fields = [ - "namespace", - "default_image", - "image_pull_policy", - "image_pull_secrets", - "service_account_name", - "supervisor_image", - "supervisor_image_pull_policy", - "grpc_endpoint", - "ssh_socket_path", - "client_tls_secret_name", - "host_gateway_ip", - "sa_token_ttl_secs", - "workspace_mode", - "gateway_id", - "workspace_default_storage_size", - "workspace_storage_class", - "default_runtime_class_name", - "supervisor_sideload_method", - "topology", - "app_armor_profile", - "sandbox_uid", - "sandbox_gid", -] -evidence = [ - "Both variants reached Ready through ServiceAccount-token exchange and gateway-minted JWT authentication, then completed callback exec.", - "The API oracle matched images, Kubernetes pull-policy spelling, pull Secret, ServiceAccount, callback and SSH environment, client TLS mount, host aliases, 600-second token projection, runc RuntimeClass, Unconfined AppArmor request, UID/GID environment, CPU/memory, managed metadata, Bound 64Mi standard PVC, and deletion.", - "The normalized baseline and candidate results are byte-identical; comparison.json records both successes, parity=true, classification=pass, and accepted=true.", -] - -[[coverage]] -id = "operator-namespace-discovery" -status = "platform_blocked" -owner = "OpenShell Kubernetes workspace-mode CI" -lane = "managed-and-operator-workspace-matrix" -fields = ["operator_namespace_label", "operator_namespace_file"] -requirement = "Run paired managed and operator workspace lifecycles with isolated namespace RBAC, label discovery, allowlist hot reload, positive placement, and negative rejection probes." - -[[coverage]] -id = "managed-ssh-ingress" -status = "platform_blocked" -owner = "OpenShell Kubernetes network-policy CI" -lane = "managed-workspace-network-policy" -fields = [ - "managed_ssh_ingress.enabled", - "managed_ssh_ingress.gateway_namespace", - "managed_ssh_ingress.gateway_pod_selector", -] -requirement = "Run paired managed-workspace creation and inspect the generated NetworkPolicy while proving the configured gateway selector can connect and a nonmatching pod cannot." - -[[coverage]] -id = "sidecar-topology" -status = "platform_blocked" -owner = "OpenShell Kubernetes sidecar CI" -lane = "kubernetes-sidecar-network-enforcement" -fields = ["sidecar.proxy_uid", "sidecar.process_binary_aware_network_policy"] -requirement = "Run paired sidecar topology with relaxed and process-aware policy profiles and verify pod security context, proxy UID, network init, and enforcement behavior." - -[[coverage]] -id = "authenticated-upstream-proxy" -status = "platform_blocked" -owner = "OpenShell Kubernetes proxy CI" -lane = "kubernetes-authenticated-connect-proxy" -fields = [ - "https_proxy", - "no_proxy", - "proxy_auth_secret_name", - "proxy_auth_secret_key", - "proxy_auth_allow_insecure", - "proxy_connect_by_hostname", -] -requirement = "Provide an authenticated HTTP CONNECT proxy and Secret, then run paired allow, deny, no-proxy, credential, insecure-opt-in, and hostname-resolution probes." - -[[coverage]] -id = "spiffe-workload-api" -status = "platform_blocked" -owner = "OpenShell SPIRE Kubernetes CI" -lane = "kubernetes-spire-csi" -fields = ["provider_spiffe_workload_api_socket_path"] -requirement = "Provide SPIRE and its CSI driver, then run paired gateway and sandbox JWT-SVID acquisition from the allowed Workload API socket path and reject a disallowed path." - -[[coverage]] -id = "user-namespace-isolation" -status = "platform_blocked" -owner = "OpenShell Kubernetes userns CI" -lane = "kubernetes-userns-supported-runtime" -fields = ["enable_user_namespaces"] -requirement = "Run paired hostUsers=false lifecycle and host-ID mapping probes on a node whose kernel, kubelet, and runtime support Kubernetes user namespaces." - -[[qualified_value]] -id = "managed-and-operator-workspace-values" -field = "workspace_mode" -status = "platform_blocked" -owner = "OpenShell Kubernetes workspace-mode CI" -lane = "managed-and-operator-workspace-matrix" -requirement = "The local core pass exercised shared mode; managed namespace creation and operator placement/rejection remain assigned live checks." - -[[qualified_value]] -id = "image-volume-sideload" -field = "supervisor_sideload_method" -status = "platform_blocked" -owner = "OpenShell Kubernetes version matrix" -lane = "kubernetes-image-volume" -requirement = "The local core pass exercised init-container; run paired image-volume lifecycle on a cluster with the ImageVolume feature enabled." - -[[qualified_value]] -id = "sidecar-topology-value" -field = "topology" -status = "platform_blocked" -owner = "OpenShell Kubernetes sidecar CI" -lane = "kubernetes-sidecar-network-enforcement" -requirement = "The local core pass exercised combined topology; sidecar requires its enforcement fixture and positive/negative network probes." - -[[qualified_value]] -id = "apparmor-enforcement" -field = "app_armor_profile" -status = "platform_blocked" -owner = "OpenShell Kubernetes AppArmor CI" -lane = "kubernetes-apparmor-runtime-default-localhost" -requirement = "The local core pass proved Unconfined API projection; an AppArmor-enabled node with an installed localhost profile must prove RuntimeDefault and Localhost enforcement." - -[[qualified_value]] -id = "runtime-class-isolation" -field = "default_runtime_class_name" -status = "platform_blocked" -owner = "OpenShell Kubernetes confidential-runtime CI" -lane = "kubernetes-kata-runtimeclass" -requirement = "The local core pass proved runc RuntimeClass placement; a configured Kata or equivalent runtime must prove isolation-specific lifecycle behavior." - -[[qualified_value]] -id = "gpu-resource-combinations" -field = "create-request.resources.gpu" -status = "platform_blocked" -owner = "OpenShell Kubernetes GPU CI" -lane = "kubernetes-nvidia-device-plugin" -requirement = "Provide NVIDIA GPU nodes and the device plugin, then run paired GPU count/resource-name combinations and verify scheduling, limits, execution, and cleanup." diff --git a/e2e/configs/gateway/schema-v2-live-results.toml b/e2e/configs/gateway/schema-v2-live-results.toml deleted file mode 100644 index 3860ac77f1..0000000000 --- a/e2e/configs/gateway/schema-v2-live-results.toml +++ /dev/null @@ -1,355 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -manifest_version = 1 -baseline_commit = "74960ebfaeec4673885089ed995fad902459749f" -candidate_start_commit = "8c868e430e9cd3284d7e274628419ab484ebcee0" - -# Step 5 establishes the portable status/create/Ready/list/exec/delete/list-empty -# contract. A platform-blocked result is not parity evidence; each blocked row -# names the lane that must replace it before the final release gate can pass. - -[[result]] -id = "portable-lifecycle-podman" -step = 5 -capability = "Portable sandbox lifecycle on the in-tree Podman compute driver" -driver = "podman" -status = "pass" -validated_baseline_commit = "74960ebfaeec4673885089ed995fad902459749f" -validated_candidate_commit = "a3860084d019ed2ac979e3eaa1ddf085a96b773c" -lane = "local-linux-x86_64-rootless-podman-5.8.2" -evidence = [ - "Paired conformance smoke connected with authenticated mTLS on both variants.", - "Both variants passed status, create, Ready inspection, paginated list visibility, exact-output exec, delete, and eventual empty-list checks.", - "Normalized comparison recorded baseline_success=true, candidate_success=true, and parity=true; equal failures are rejected by the harness.", -] - -[[result]] -id = "portable-lifecycle-docker" -step = 5 -capability = "Portable sandbox lifecycle on the in-tree Docker compute driver" -driver = "docker" -status = "platform_blocked" -owner = "OpenShell Linux Docker CI lane" -lane = "linux-x86_64-docker" -blocker = "The validation host has no Docker CLI or Docker daemon socket. Step 7 and Step 13 must execute and assign this lane." - -[[result]] -id = "portable-lifecycle-kubernetes" -step = 5 -capability = "Portable sandbox lifecycle on the in-tree Kubernetes compute driver" -driver = "kubernetes" -status = "platform_blocked" -owner = "OpenShell Kubernetes CI lane" -lane = "ephemeral-kind-or-managed-kubernetes" -blocker = "The active kubectl context is an external OpenShift cluster that this validation must not modify. The installed kind cluster belongs to another project; Step 8 and Step 13 must use a dedicated OpenShell cluster." - -[[result]] -id = "portable-lifecycle-vm" -step = 5 -capability = "Portable sandbox lifecycle on the standalone VM compute driver" -driver = "vm" -status = "platform_blocked" -owner = "OpenShell Linux VM CI lane" -lane = "linux-x86_64-kvm-libkrun" -blocker = "The validation host has no prepared VM runtime bundle or built openshell-driver-vm executable. Step 9 and Step 13 must execute and assign this lane." - -[[result]] -id = "portable-lifecycle-mxc" -step = 5 -capability = "Portable sandbox lifecycle on the Windows MXC compute driver" -driver = "mxc" -status = "platform_blocked" -owner = "OpenShell Windows MXC CI lane" -lane = "windows-x64-and-windows-arm64-mxc" -blocker = "The validation host is Linux and cannot execute the Windows MXC runtime. Step 13 must assign native Windows validation." - -[[result]] -id = "gateway-wide-process-options" -step = 6 -capability = "Gateway listeners, configuration precedence, persistence, and legacy singleton selector" -driver = "gateway" -status = "pass" -validated_baseline_commit = "74960ebfaeec4673885089ed995fad902459749f" -validated_candidate_commit = "e6aac1aa7c624c5df43535ab4fbe2bc6f9697dea" -lane = "local-linux-x86_64-rootless-podman-5.8.2" -evidence = [ - "Fresh binaries from both recorded commits started against isolated schema-v1 and schema-v2 files, SQLite databases, ports, state directories, and a rootless Podman socket.", - "Both variants exposed primary, health, and metrics listeners; the CLI primary port beat conflicting environment and file values, and --health-port 0 disabled the file-configured auxiliary listener.", - "Both variants created and reopened the same isolated SQLite database, rejected database_url in TOML, identified the rejected field, and did not disclose its secret-bearing value.", - "Both variants accepted one legacy OPENSHELL_DRIVERS=podman selector when no canonical selector was present; the candidate emitted its deprecation warning without logging the value.", - "Normalized comparison recorded baseline_success=true, candidate_success=true, and parity=true.", -] - -[[result]] -id = "gateway-tls-client-auth-policy" -step = 6 -capability = "Gateway TLS client-certificate handshake policy" -driver = "gateway" -status = "intentional_change" -validated_baseline_commit = "74960ebfaeec4673885089ed995fad902459749f" -validated_candidate_commit = "e6aac1aa7c624c5df43535ab4fbe2bc6f9697dea" -lane = "local-linux-x86_64-deterministic-tls" -evidence = [ - "Security review confirmed that origin/main accepted require_client_auth in TOML but ignored it and instead derived runtime policy from client CA and OIDC presence.", - "Schema v2 now rejects the unsupported file field while preserving the established derived policy: CA without OIDC requires a client certificate, while CA with OIDC permits bearer-only clients and validates certificates that are presented.", - "Focused preparation tests cover both derived branches, and TLS integration tests pass required-certificate, optional-certificate, valid-CA, wrong-CA, and multiplexed HTTP/gRPC handshakes.", -] - -[[result]] -id = "docker-driver-option-parity" -step = 7 -capability = "Docker image, callback, pull-policy, resource, security, provider, and mount options" -driver = "docker" -status = "platform_blocked" -owner = "OpenShell Linux Docker CI lane" -lane = "linux-x86_64-docker-with-apparmor-sub-lane" -blocker = "The validation host has neither a Docker CLI nor a Docker daemon, and the Docker driver deliberately rejects the available Libpod socket. A dedicated Docker lane must run paired lifecycle and inspect-based resource, label, mount, callback, and pull-policy checks; candidate-only AppArmor, authenticated proxy, and SPIFFE checks must run on qualifying daemons." - -[[result]] -id = "podman-driver-option-parity" -step = 7 -capability = "Podman image, callback, pull-policy, resource, mount, bootstrap, SSH, and health options" -driver = "podman" -status = "intentional_change" -validated_baseline_commit = "74960ebfaeec4673885089ed995fad902459749f" -validated_candidate_commit = "e09070d4ba0b30f0ac278fbb9938c1520ecff696" -lane = "local-linux-x86_64-rootless-podman-5.8.2" -evidence = [ - "Fresh binaries from both recorded commits ran the same candidate-owned oracle against isolated schema-v1 and schema-v2 gateway processes, state, PKI, sockets, networks, and container stores.", - "Both variants selected the same immutable sandbox image, mapped their schema-specific pull-policy spelling to Podman missing, applied 750m CPU and 384Mi memory limits, preserved managed labels, mounted read-only bind and tmpfs configuration, injected sandbox-token and guest-TLS bootstrap files, used the renamed SSH socket, became healthy at the configured seven-second interval, and completed callback exec.", - "The normalized results were identical except for pids_limit: the frozen baseline accepted 31 but sent Docker's ignored PidsLimit field and received Podman's 2048 default, while the candidate sent OCI resource_limits.pids.limit and applied 31.", - "comparison.json recorded baseline_success=true, candidate_success=true, parity=false, classification=intentional_change, intentional_change_id=podman-pid-limit-restored, and accepted=true.", -] - -[[result]] -id = "podman-qualified-security-option-parity" -step = 7 -capability = "Podman AppArmor, authenticated proxy, SPIFFE, and rootful user-namespace options" -driver = "podman" -status = "platform_blocked" -owner = "OpenShell Podman security matrix" -lane = "linux-podman-apparmor-proxy-spiffe-rootful-userns" -blocker = "The available daemon is rootless and reports AppArmor disabled, and this local run has no authenticated proxy or SPIFFE Workload API fixture. A qualifying rootful matrix must validate those environment-dependent options live; deterministic driver tests remain coverage, not parity evidence." - -[[result]] -id = "kubernetes-core-option-parity" -step = 8 -capability = "Kubernetes shared workspace, images, placement, bootstrap, security projection, resources, and combined supervisor options" -driver = "kubernetes" -status = "pass" -validated_baseline_commit = "74960ebfaeec4673885089ed995fad902459749f" -validated_candidate_commit = "0f08b5822e4da98c9ced3d4b0f2bf4f30dae28fd" -lane = "kind-v1.36.1-rootless-podman-host-gateway" -evidence = [ - "Fresh candidate gateway and CLI binaries were built from the recorded candidate commit and bound by SHA-256, with the frozen baseline binary, in the retained artifact manifest.", - "Both host gateway variants used isolated schema-v1/schema-v2 files, SQLite stores, JWT keys, ports, namespaces, ServiceAccounts, Secrets, and resource names against a guarded disposable kind cluster.", - "Both variants reached Ready through authenticated callback, completed exact-marker exec, and produced matching Sandbox, Pod, and PVC semantics for images and pull policies, pull Secret, ServiceAccount, TLS and token projections, host aliases, SSH endpoint, runc placement, AppArmor request, UID/GID, CPU/memory, shared storage, managed metadata, and deletion.", - "comparison.json recorded baseline_success=true, candidate_success=true, parity=true, classification=pass, and accepted=true; private keys and all per-run Kubernetes fixtures were removed.", -] - -[[result]] -id = "kubernetes-qualified-option-parity" -step = 8 -capability = "Kubernetes managed/operator workspaces, sidecar enforcement, proxy, SPIFFE, user namespaces, AppArmor enforcement, confidential runtimes, and GPUs" -driver = "kubernetes" -status = "platform_blocked" -owner = "OpenShell Kubernetes qualified option matrix" -lane = "kubernetes-managed-operator-sidecar-proxy-spire-userns-apparmor-kata-gpu" -blocker = "The disposable core lane executed shared, combined, init-container, runc, and Unconfined values but did not execute the infrastructure-qualified alternatives. Assigned lanes must run paired managed/operator namespace discovery, sidecar network enforcement, authenticated proxy, SPIRE CSI, hostUsers=false, AppArmor RuntimeDefault/Localhost, image-volume, Kata RuntimeClass, and NVIDIA device-plugin GPU probes." - -[[result]] -id = "vm-launch-and-resource-configuration" -step = 9 -capability = "VM launch, callback, persistent state, images, resources, and guest identity" -driver = "vm" -status = "platform_blocked" -owner = "OpenShell Linux VM CI lane" -lane = "linux-x86_64-kvm-libkrun" -blocker = "This host has no prepared VM runtime bundle and no frozen-baseline or candidate openshell-driver-vm executable linked to a runnable libkrun environment. Both source trees passed their deterministic VM library suites, but only a paired KVM/libkrun lane can prove Ready, exec, restart recovery, overlay persistence, resource sizing, callback equivalence, and deletion." - -[[result]] -id = "vm-guest-security-and-spiffe" -step = 9 -capability = "VM guest token containment, TLS, owner state, proxy credentials, and SPIFFE opt-in" -driver = "vm" -status = "platform_blocked" -owner = "OpenShell Linux VM security CI lane" -lane = "linux-x86_64-kvm-libkrun-proxy-spiffe" -blocker = "The guest JWT mode and visibility, private owner-marker and sandbox-state persistence, callback recovery, TLS key permissions, proxy credential containment, and guest-reachable SPIFFE TCP behavior require a booted VM. The assigned lane must add authenticated-proxy and Workload API TCP fixtures to the paired libkrun/KVM run." - -[[result]] -id = "compute-driver-boundary-parity" -step = 10 -capability = "In-tree and external-UDS compute-driver execution with sandbox callback connectivity" -driver = "podman" -status = "pass" -validated_baseline_commit = "74960ebfaeec4673885089ed995fad902459749f" -validated_candidate_commit = "4a39da510e4d278a24dd60291149519c9a570b46" -lane = "local-linux-x86_64-rootless-podman-5.8.2" -evidence = [ - "Fresh exact-source gateway, CLI, conformance, Podman driver, and supervisor artifacts were staged before execution, hashed, executed from retained staging directories, and verified unchanged after execution.", - "Driver-free baseline and candidate gateways completed the same authenticated status, create, Ready, list, callback-exec, delete, and list-empty oracle through distinct-content external-driver executables over separate UDS endpoints; in-tree gateways completed the same oracle.", - "External drivers ran under env -i with a complete allowlisted environment attestation covering compute and Podman sockets, callback endpoint and TLS-file hashes, pull policy, images, network, timeouts, and bind mounts; successful driver logs were retained and hashed.", - "The verifier bound each transport-only gateway socket_path to the driver process input and required distinct baseline and candidate compute sockets, Podman sockets, networks, and callback TLS paths.", - "All four runs executed one repository-digest-pinned sandbox image and matched its image ID and manifest digest. They also matched the supervisor base-image identity, digest-pinned base reference, source Dockerfile, installed-package manifest, and same-source runtime artifacts across topologies.", - "Each variant built its supervisor image from its own staged supervisor binary in a forced temporary Podman service and isolated store; all supervisor runtime image references were digest-pinned.", - "A staged and hashed transparent CLI wrapper retained exact exec stdout bytes; the verifier tied them to the unique conformance run ID, required authenticated preflight and every lifecycle exit 0, and hashed result, launch, driver, raw-log, exec-stdout, gateway-configuration, package, and staged-artifact evidence.", -] - -# Step 11 inventories the remaining cross-cutting capabilities. Paired -# deterministic suites passed, but they are regression preflight rather than -# live parity evidence. Every row therefore remains assigned to a live lane. - -[[result]] -id = "oidc-bearer-authentication" -step = 11 -capability = "OIDC bearer token validation, role and scope authorization, and JWKS refresh" -driver = "auth" -status = "platform_blocked" -owner = "OpenShell auth and OIDC CI lane" -lane = "linux-oidc-paired-keycloak-jwks" -blocker = "The host has no isolated paired issuer fixture that presents equivalent signed tokens to both exact-source gateways. Existing Keycloak and OIDC suites are candidate-oriented and cannot establish schema-v1/schema-v2 audience, role, scope, expiry, signature, and key-rotation parity without a shared candidate-owned oracle." - -[[result]] -id = "mtls-user-authentication" -step = 11 -capability = "mTLS client-certificate user identity and authorization" -driver = "auth" -status = "platform_blocked" -owner = "OpenShell gateway authentication CI lane" -lane = "linux-mtls-user-principal-paired" -blocker = "The paired TLS integration tests validate handshake policy but do not drive an authenticated user RPC through both gateways or prove certificate-to-principal mapping. A live lane must use isolated client PKI, equivalent schema files, and the same authorization oracle for enabled and disabled mTLS user authentication." - -[[result]] -id = "unsafe-unauthenticated-user-mode" -step = 11 -capability = "Explicit trusted-development unauthenticated user mode with authenticated sandbox callbacks" -driver = "auth" -status = "platform_blocked" -owner = "OpenShell gateway authentication CI lane" -lane = "linux-auth-chain-paired-isolated" -blocker = "No paired live fixture currently proves that the trusted-development switch admits user requests while sandbox callbacks continue to require sandbox credentials. Candidate-only extension examples use this switch for setup, but that is not an authentication-boundary parity test." - -[[result]] -id = "gateway-minted-sandbox-jwt" -step = 11 -capability = "Gateway-minted sandbox and extension JWT claims and lifetime semantics" -driver = "auth" -status = "platform_blocked" -owner = "OpenShell gateway JWT CI lane" -lane = "linux-gateway-jwt-claims-paired" -blocker = "Step 10 proved authenticated callback connectivity but did not capture and compare token claims. A controlled callback and extension fixture must verify issuer, gateway identity, key ID, audience, subject, token type, and expiry semantics for both gateways; explicit zero versus omitted TTL remains the ledgered gateway-jwt-zero-sentinel-removed intentional change." - -[[result]] -id = "otlp-observability" -step = 11 -capability = "Gateway and in-tree driver OTLP traces and failure isolation" -driver = "observability" -status = "platform_blocked" -owner = "OpenShell observability CI lane" -lane = "linux-otlp-grpc-collector-paired" -blocker = "The deterministic exporter tests passed, but no retained paired collector capture proves emitted gateway and in-tree-driver spans, gateway name, service name, selected-driver resource attributes, or continued serving after collector failure. A loopback OTLP/gRPC collector lane must observe those outcomes from both exact-source processes." - -[[result]] -id = "gateway-interceptor-registration" -step = 11 -capability = "Gateway interceptor registration, binding, mutation, and failure behavior" -driver = "gateway-interceptor" -status = "platform_blocked" -owner = "OpenShell gateway interceptor CI lane" -lane = "linux-gateway-interceptor-paired" -blocker = "The interceptor library suite passed on both revisions, while the governance-interceptor smoke test remains candidate-only. A paired service must compare Describe validation, binding selection, unary request and response mutation, failure policy, timeout and size limits, and secret exclusion against both gateway schema variants." - -[[result]] -id = "supervisor-middleware-registration" -step = 11 -capability = "Supervisor middleware registration, policy distribution, and enforcement" -driver = "supervisor-middleware" -status = "platform_blocked" -owner = "OpenShell supervisor middleware CI lane" -lane = "linux-supervisor-middleware-paired" -blocker = "The middleware library suite passed on both revisions, while the content-guard smoke test remains candidate-only. A paired gateway, sandbox, and middleware fixture must compare Describe negotiation, policy distribution, guarded and unguarded traffic, failure policy, timeout, and payload limits; the field-name normalization remains the ledgered middleware-payload-name-normalized intentional change." - -[[result]] -id = "provider-profile-sources" -step = 11 -capability = "Builtin, user, and interceptor provider-profile source composition" -driver = "provider-profiles" -status = "platform_blocked" -owner = "OpenShell provider profile CI lane" -lane = "linux-provider-profiles-interceptor-paired" -blocker = "Server tests cover source construction and duplicate rejection, but no shared live interceptor catalog has been queried through both gateways. The assigned lane must compare builtin, user, and interceptor source ordering, normalized profile identities, duplicate rejection, discovery output, and failure handling with isolated persistent state." - -[[result]] -id = "inference-control-plane-configuration" -step = 11 -capability = "Persisted provider and route configuration delivered to sandbox inference" -driver = "inference" -status = "platform_blocked" -owner = "OpenShell inference E2E lane" -lane = "linux-inference-provider-routing-paired" -blocker = "Existing inference routing tests are candidate-oriented and this host has no assigned paired provider and model-service fixture. The lane must configure providers and routes through each gateway control plane, observe the effective supervisor bundle, and compare model discovery and an inference request without reusing database state." - -[[result]] -id = "credential-driver-selection-and-kek" -step = 11 -capability = "Credential-driver selection, KEK handling, encrypted storage, and secret containment" -driver = "credentials" -status = "platform_blocked" -owner = "OpenShell credential security CI lane" -lane = "linux-credential-kek-paired" -blocker = "Both server suites passed deterministic selection, KEK, and credential tests, but they are revision-local tests rather than one candidate-owned live oracle. A paired lane must start isolated gateways with independent databases and KEKs, compare default encrypted storage and invalid selections, store and resolve opaque credentials, and verify that logs and configuration do not disclose secrets." - -[[result]] -id = "credential-driver-backend-tables" -step = 11 -capability = "In-tree and remote credential-driver transport and opaque credential lifecycle" -driver = "credential-driver" -status = "platform_blocked" -owner = "OpenShell credential driver E2E lane" -lane = "kubernetes-vault-uds-credential-drivers-paired" -blocker = "The host has no assigned disposable Kubernetes Secrets or Vault backend and no retained paired UDS credential-driver execution. Existing backend E2E coverage is candidate-oriented. The assigned lane must compare in-tree and remote transport validation plus opaque store and retrieve behavior using isolated namespaces, Vault state, sockets, databases, and credentials." - -# Step 12 resolves deterministic package-startup blockers without claiming that -# source-tree tests substitute for installation, upgrade, or refresh evidence. - -[[result]] -id = "rpm-package-upgrade" -step = 12 -capability = "RPM schema-v1 default migration and operator-edited configuration preservation" -driver = "packaging" -status = "platform_blocked" -owner = "OpenShell RPM package upgrade CI lane" -lane = "fedora-rpm-prior-release-upgrade" -blocker = "Deterministic migration tests prove exact-default replacement, edited-file preservation, mode preservation, idempotence, and unsafe-path rejection, but no prior RPM was installed and upgraded on this host. The assigned lane must verify package ownership and permissions, service restart, exact and edited schema-v1 files, and rollback-safe failure behavior." - -[[result]] -id = "debian-package-upgrade" -step = 12 -capability = "Debian prior-artifact upgrade with schema-v1 configuration preservation" -driver = "packaging" -status = "platform_blocked" -owner = "OpenShell Debian package upgrade CI lane" -lane = "ubuntu-debian-prior-release-upgrade" -blocker = "The source-tree tests prove Debian preflight selection, source-free diagnostics, non-mutation, and ordering before certificate generation, but dpkg-deb and an installed prior Debian artifact are unavailable. The assigned lane must preserve generated and edited schema-v1 files, exercise manual conversion, and prove successful schema-v2 service restart after upgrade." - -[[result]] -id = "snap-package-refresh" -step = 12 -capability = "Snap prior-release refresh with schema-v1 configuration preservation" -driver = "packaging" -status = "platform_blocked" -owner = "OpenShell Snap refresh CI lane" -lane = "ubuntu-snap-prior-release-refresh" -blocker = "The source-tree tests prove Snap config precedence, preflight failure handling, and non-mutation, but this host cannot install and refresh confined Snap revisions. The assigned lane must refresh a prior Snap with generated and edited schema-v1 files, exercise manual conversion, and prove successful schema-v2 daemon startup." - -[[result]] -id = "homebrew-package-upgrade" -step = 12 -capability = "Homebrew prior-release upgrade with package-default and operator configuration preservation" -driver = "packaging" -status = "platform_blocked" -owner = "OpenShell macOS Homebrew package upgrade CI lane" -lane = "macos-homebrew-prior-release-upgrade" -blocker = "No Homebrew formula installation or prior-release upgrade ran on this Linux host. The assigned macOS lane must upgrade a prior formula with recognized package defaults and edited schema-v1 configuration, verify only provable defaults migrate automatically, and prove successful schema-v2 service restart." diff --git a/e2e/configs/gateway/schema-v2-parity-gap-dispositions.toml b/e2e/configs/gateway/schema-v2-parity-gap-dispositions.toml deleted file mode 100644 index 473e092433..0000000000 --- a/e2e/configs/gateway/schema-v2-parity-gap-dispositions.toml +++ /dev/null @@ -1,106 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Reviewed potential blockers for the schema-v2 parity campaign. A disposition -# records whether the branch must change before parity can be claimed; it does -# not record test execution results. -ledger_version = 1 -issue = 2792 -baseline_commit = "74960ebfaeec4673885089ed995fad902459749f" -candidate_start_commit = "8c868e430e9cd3284d7e274628419ab484ebcee0" - -[[gaps]] -id = "legacy-environment-selector-upgrade" -severity = "none" -parity_relation = "regression" -disposition = "resolved" -origin_main_behavior = "OPENSHELL_DRIVERS accepts one driver name and is loaded by RPM, Debian, and Homebrew gateway services through gateway.env." -candidate_behavior = "Schema v2 accepts one non-empty OPENSHELL_DRIVERS value as a deprecated environment-only alias, rejects ambiguity and conflicts, and keeps removed CLI flags rejected." -impact = "The package-upgrade regression is resolved while preserving singular selector semantics and actionable migration guidance." -resolution = "The gateway now accepts one non-empty OPENSHELL_DRIVERS value, preserves canonical selector precedence, rejects multiple values and conflicting canonical and legacy values, and emits one deprecation warning after tracing initialization without logging the selector." -validation = "Paired frozen-baseline and candidate processes reached readiness through OPENSHELL_DRIVERS=podman; focused tests cover empty, plural, malformed, non-UTF-8, equal canonical, conflicting canonical, and remote-socket cases." -owner_step = 6 - -[[gaps]] -id = "debian-snap-v1-upgrade" -severity = "none" -parity_relation = "upgrade_regression" -disposition = "resolved" -origin_main_behavior = "Debian auto-discovers a persistent schema-v1 XDG gateway.toml and Snap passes a persistent schema-v1 SNAP_COMMON gateway.toml." -candidate_behavior = "Debian and Snap run the gateway's source-free, read-only preflight against the same effectively selected config before certificate generation or daemon startup; rejected legacy files, schema/layout errors, and documented cross-field startup errors remain unchanged with actionable migration guidance. Selected driver-specific fields remain lazily validated at driver construction." -impact = "The package startup regression is resolved without inferring provenance or overwriting operator-owned configuration; release remains gated on real Debian upgrade and Snap refresh execution." -resolution = "Use the shared strict schema-v2 layout parser plus documented read-only effective startup checks, fail closed for explicit, unsafe, or legacy paths, preserve optional absence, and require manual migration because historical Debian and Snap generators have no safe default-provenance marker." -validation = "Deterministic Rust, shell, and Python tests cover selection precedence, stable diagnostics, content non-disclosure, semantic checks, file preservation, and package ordering. Real prior Debian upgrade and Snap refresh lanes remain platform-blocked and must verify preserved v1 files plus successful restart after manual conversion." -owner_step = 12 - -[[gaps]] -id = "tls-require-client-auth-ignored" -severity = "none" -parity_relation = "preexisting_inaccessible_option" -disposition = "resolved" -origin_main_behavior = "The TOML schema accepts and documents require_client_auth, but runtime derives the value from client CA presence and OIDC instead of using the configured boolean." -candidate_behavior = "Schema v2 rejects require_client_auth and documents the derived client CA and OIDC policy instead of silently accepting an ineffective security option." -impact = "The misleading security-sensitive configuration claim is removed without weakening CA-only gateways or breaking bearer-only OIDC clients." -resolution = "Security review selected removal over runtime wiring: the file-only TLS schema excludes require_client_auth while the internal runtime field continues enforcing the established client CA and OIDC policy." -validation = "File parsing rejects require_client_auth; preparation tests cover CA-only and CA-plus-OIDC derivation; TLS integration tests cover required, optional, valid-CA, wrong-CA, and no-certificate handshakes." -owner_step = 6 - -[[gaps]] -id = "unselected-driver-validation-claim" -severity = "none" -parity_relation = "documentation_gap" -disposition = "resolved" -origin_main_behavior = "Only the selected driver table receives driver-specific deserialization; unknown fields inside an unselected table are ignored." -candidate_behavior = "Schema v2 validates every driver entry is a table and lazily validates driver-specific fields only when that driver is selected, as the reference now states." -impact = "The documentation now distinguishes table-shape validation from selected-driver deserialization, so it no longer overstates startup guarantees." -resolution = "The gateway configuration reference was narrowed to state that driver-specific fields are validated when their driver is selected." -validation = "Configuration tests establish that every driver entry must be a TOML table and that selected driver tables reject unknown fields; the published reference describes the lazy boundary." -owner_step = 6 - -[[gaps]] -id = "multi-driver-runtime-loss" -severity = "none" -parity_relation = "non_finding" -disposition = "no_action" -origin_main_behavior = "Schema v1 accepts a list-shaped selector but runtime rejects configurations containing more than one driver." -candidate_behavior = "Schema v2 represents the existing invariant as one optional scalar driver." -impact = "No working multi-driver runtime capability was removed." -resolution = "Keep the singular selector in the intentional-change ledger and validate explicit selection plus auto-detection." -validation = "Confirm origin/main rejects two configured drivers and the candidate supports one scalar or omission." -owner_step = 5 - -[[gaps]] -id = "generated-e2e-selector-shape" -severity = "none" -parity_relation = "non_finding" -disposition = "no_action" -origin_main_behavior = "Committed E2E fixtures select one driver through the schema-v1 list." -candidate_behavior = "Committed Docker and Podman fixtures and e2e/run.sh consistently use and require the schema-v2 scalar selector." -impact = "No committed fixture mismatch was found." -resolution = "Use the existing fixtures in live Docker and Podman parity lanes." -validation = "Run both driver E2E wrappers and confirm the selected runtime creates a sandbox." -owner_step = 5 - -[[gaps]] -id = "rpm-exact-default-migration" -severity = "none" -parity_relation = "non_finding" -disposition = "no_action" -origin_main_behavior = "The RPM service seeds its package-owned schema-v1 default." -candidate_behavior = "The RPM pre-start migrator replaces only a byte-identical package v1 default and preserves edited or unsafe paths." -impact = "The implemented exact-default path does not block parity, but environment-file and edited-config behavior remain covered by separate gaps." -resolution = "Retain deterministic migration tests and add a real package upgrade lane." -validation = "Upgrade an installed prior RPM with exact and edited defaults and verify restart, bytes, ownership, and mode." -owner_step = 12 - -[[gaps]] -id = "gateway-owned-guest-tls" -severity = "none" -parity_relation = "non_finding" -disposition = "no_action" -origin_main_behavior = "Guest callback TLS may be inherited from gateway scope or overridden in the selected local driver table." -candidate_behavior = "One complete gateway-owned bundle is validated and injected into the selected local driver; misplaced driver fields fail explicitly." -impact = "The ownership change fails closed rather than silently disabling callback TLS." -resolution = "Keep the ownership change in the intentional-change ledger and validate callback connectivity on Docker, Podman, and VM." -validation = "Exercise complete, partial, misplaced, and plaintext combinations, then establish a real sandbox callback on each local driver." -owner_step = 10 diff --git a/e2e/configs/gateway/schema-v2-step11-baseline-attestation.txt b/e2e/configs/gateway/schema-v2-step11-baseline-attestation.txt deleted file mode 100644 index 823c5dd1ba..0000000000 --- a/e2e/configs/gateway/schema-v2-step11-baseline-attestation.txt +++ /dev/null @@ -1,101 +0,0 @@ -schema_v2_step11_deterministic_attestation_version=1 -variant=baseline -source_commit=74960ebfaeec4673885089ed995fad902459749f -source_tree_clean=true -cargo_target_scope=checkout-local -rustc_wrapper=disabled -cargo 1.95.0 (f2d3ce0bd 2026-03-21) -rustc 1.95.0 (59807616e 2026-04-14) - -=== suite:server-lib === -command=cargo test -q -p openshell-server --lib ---- output --- - -running 1463 tests -....................................................................................... 87/1463 -....................................................................................... 174/1463 -...............................................................................i....... 261/1463 -......................i................................................................ 348/1463 -..............................................................................i........ 435/1463 -....................................................................................... 522/1463 -....................................................................................... 609/1463 -....................................................................................... 696/1463 -....................................................................................... 783/1463 -......................................................................................i 870/1463 -....................................................................................... 957/1463 -....................................................................................... 1044/1463 -....................................................................................... 1131/1463 -................................................................................i...... 1218/1463 -.........................................ii............................................ 1305/1463 -...............................i....................................................... 1392/1463 -....................................................................... -test result: ok. 1455 passed; 0 failed; 8 ignored; 0 measured; 0 filtered out; finished in 9.13s - ---- exit_status:0 --- - -=== suite:gateway-interceptors === -command=cargo test -q -p openshell-gateway-interceptors ---- output --- - -running 49 tests -................................................. -test result: ok. 49 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.32s - - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - ---- exit_status:0 --- - -=== suite:supervisor-middleware === -command=cargo test -q -p openshell-supervisor-middleware ---- output --- - -running 85 tests -..................................................................................... -test result: ok. 85 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.52s - - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - ---- exit_status:0 --- - -=== suite:otel-test-support === -command=cargo test -q -p openshell-otel-test-support ---- output --- - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - ---- exit_status:0 --- - -=== suite:multiplex-tls-integration === -command=cargo test -q -p openshell-server --test multiplex_tls_integration ---- output --- - -running 5 tests -..... -test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.14s - ---- exit_status:0 --- - -=== suite:edge-tunnel-auth === -command=cargo test -q -p openshell-server --test edge_tunnel_auth ---- output --- - -running 5 tests -..... -test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s - ---- exit_status:0 --- - -attestation_complete=true diff --git a/e2e/configs/gateway/schema-v2-step11-candidate-attestation.txt b/e2e/configs/gateway/schema-v2-step11-candidate-attestation.txt deleted file mode 100644 index 5b2cab4905..0000000000 --- a/e2e/configs/gateway/schema-v2-step11-candidate-attestation.txt +++ /dev/null @@ -1,102 +0,0 @@ -schema_v2_step11_deterministic_attestation_version=1 -variant=candidate -source_commit=363d8540830b2ea294d43198daa2b7a283a2face -source_tree_clean=true -cargo_target_scope=checkout-local -rustc_wrapper=disabled -cargo 1.95.0 (f2d3ce0bd 2026-03-21) -rustc 1.95.0 (59807616e 2026-04-14) - -=== suite:server-lib === -command=cargo test -q -p openshell-server --lib ---- output --- - -running 1489 tests -....................................................................................... 87/1489 -....................................................................................... 174/1489 -....................................................................................... 261/1489 -...........i.............................i............................................. 348/1489 -....................................................................................... 435/1489 -................i...................................................................... 522/1489 -....................................................................................... 609/1489 -....................................................................................... 696/1489 -....................................................................................... 783/1489 -....................................................................................... 870/1489 -........................i.............................................................. 957/1489 -....................................................................................... 1044/1489 -....................................................................................... 1131/1489 -....................................................................................... 1218/1489 -..................i...............................................ii................... 1305/1489 -........................................................i.............................. 1392/1489 -....................................................................................... 1479/1489 -.......... -test result: ok. 1481 passed; 0 failed; 8 ignored; 0 measured; 0 filtered out; finished in 9.12s - ---- exit_status:0 --- - -=== suite:gateway-interceptors === -command=cargo test -q -p openshell-gateway-interceptors ---- output --- - -running 49 tests -................................................. -test result: ok. 49 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s - - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - ---- exit_status:0 --- - -=== suite:supervisor-middleware === -command=cargo test -q -p openshell-supervisor-middleware ---- output --- - -running 85 tests -..................................................................................... -test result: ok. 85 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.51s - - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - ---- exit_status:0 --- - -=== suite:otel-test-support === -command=cargo test -q -p openshell-otel-test-support ---- output --- - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - ---- exit_status:0 --- - -=== suite:multiplex-tls-integration === -command=cargo test -q -p openshell-server --test multiplex_tls_integration ---- output --- - -running 5 tests -..... -test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s - ---- exit_status:0 --- - -=== suite:edge-tunnel-auth === -command=cargo test -q -p openshell-server --test edge_tunnel_auth ---- output --- - -running 5 tests -..... -test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s - ---- exit_status:0 --- - -attestation_complete=true diff --git a/e2e/parity/gateway-options.sh b/e2e/parity/gateway-options.sh deleted file mode 100755 index 1a0a98a0d4..0000000000 --- a/e2e/parity/gateway-options.sh +++ /dev/null @@ -1,224 +0,0 @@ -#!/usr/bin/env bash -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Paired process-level checks for gateway-wide options that do not require a -# sandbox. The caller supplies immutable baseline and candidate gateway builds. - -set -euo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -BASELINE_GATEWAY="${OPENSHELL_PARITY_BASELINE_GATEWAY_BIN:-}" -CANDIDATE_GATEWAY="${OPENSHELL_PARITY_CANDIDATE_GATEWAY_BIN:-}" -RESULTS_DIR="${OPENSHELL_PARITY_RESULTS_DIR:-${ROOT}/target/parity/gateway-options}" -BASELINE_SHA="74960ebfaeec4673885089ed995fad902459749f" -CANDIDATE_SHA="$(git -C "${ROOT}" rev-parse HEAD)" -WORKDIR="$(mktemp -d "${TMPDIR:-/tmp}/openshell-gateway-options.XXXXXX")" -PIDS=() - -cleanup() { - local status=$? pid - for pid in "${PIDS[@]}"; do - kill -INT "${pid}" >/dev/null 2>&1 || true - wait "${pid}" >/dev/null 2>&1 || true - done - rm -rf "${WORKDIR}" - exit "${status}" -} -trap cleanup EXIT - -fail() { - echo "ERROR: $*" >&2 - exit 1 -} - -for binary in "${BASELINE_GATEWAY}" "${CANDIDATE_GATEWAY}"; do - [ -x "${binary}" ] || fail "gateway binary is not executable: ${binary:-}" -done -command -v curl >/dev/null 2>&1 || fail "curl is required" -command -v podman >/dev/null 2>&1 || fail "podman is required" -podman info >/dev/null 2>&1 || fail "podman service is not reachable" - -PODMAN_SOCKET="${OPENSHELL_PODMAN_SOCKET:-${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/podman/podman.sock}" -[ -S "${PODMAN_SOCKET}" ] || fail "Podman API socket is unavailable: ${PODMAN_SOCKET}" -mkdir -p "${RESULTS_DIR}" - -pick_port() { - python3 - <<'PY' -import socket -with socket.socket() as sock: - sock.bind(("127.0.0.1", 0)) - print(sock.getsockname()[1]) -PY -} - -write_config() { - local output=$1 schema=$2 variant=$3 file_port=$4 health_port=$5 metrics_port=$6 - cat >"${output}" <>"${output}" - else - printf 'compute_driver = "podman"\n' >>"${output}" - fi - cat >>"${output}" <<'EOF' - -[openshell.gateway.auth] -allow_unauthenticated_users = true - -[openshell.drivers.podman] -EOF -} - -wait_for_url() { - local pid=$1 url=$2 log=$3 elapsed=0 - while [ "${elapsed}" -lt 100 ]; do - if curl --noproxy '*' --max-time 1 -fsS "${url}" >/dev/null 2>&1; then - return 0 - fi - if ! kill -0 "${pid}" >/dev/null 2>&1; then - echo "=== gateway log ===" >&2 - cat "${log}" >&2 || true - return 1 - fi - sleep 0.1 - elapsed=$((elapsed + 1)) - done - echo "timed out waiting for ${url}" >&2 - cat "${log}" >&2 || true - return 1 -} - -stop_gateway() { - local pid=$1 - kill -INT "${pid}" >/dev/null 2>&1 || true - wait "${pid}" >/dev/null 2>&1 || true - PIDS=() -} - -run_variant() { - local variant=$1 schema=$2 sha=$3 gateway=$4 - local dir="${WORKDIR}/${variant}" - local config="${dir}/gateway.toml" db="${dir}/gateway.db" log="${dir}/gateway.log" - local file_port env_port primary_port health_port metrics_port second_primary - mkdir -p "${dir}/state" - file_port="$(pick_port)" - env_port="$(pick_port)" - primary_port="$(pick_port)" - health_port="$(pick_port)" - metrics_port="$(pick_port)" - second_primary="$(pick_port)" - write_config "${config}" "${schema}" "${variant}" "${file_port}" "${health_port}" "${metrics_port}" - - echo "==> ${variant}: precedence, listeners, and initial SQLite open" - XDG_CONFIG_HOME="${dir}/config" \ - XDG_STATE_HOME="${dir}/state" \ - OPENSHELL_PODMAN_SOCKET="${PODMAN_SOCKET}" \ - OPENSHELL_SERVER_PORT="${env_port}" \ - "${gateway}" \ - --config "${config}" \ - --db-url "sqlite:${db}?mode=rwc" \ - --name "${variant}-cli" \ - --port "${primary_port}" \ - --log-level debug >"${log}" 2>&1 & - local pid=$! - PIDS=("${pid}") - wait_for_url "${pid}" "http://127.0.0.1:${health_port}/healthz" "${log}" \ - || fail "${variant} health listener did not start" - curl --noproxy '*' --max-time 2 -fsS "http://127.0.0.1:${metrics_port}/metrics" >/dev/null \ - || fail "${variant} metrics listener is unavailable" - # A plain HTTP request to the gRPC listener returns 404; successful TCP/HTTP - # exchange is sufficient to prove that the selected primary port is bound. - curl --noproxy '*' --max-time 2 -sS "http://127.0.0.1:${primary_port}/" >/dev/null \ - || fail "${variant} primary listener is unavailable" - if curl --noproxy '*' --max-time 1 -sS "http://127.0.0.1:${file_port}/" >/dev/null 2>&1; then - fail "${variant} file port unexpectedly beat the CLI port" - fi - if curl --noproxy '*' --max-time 1 -sS "http://127.0.0.1:${env_port}/" >/dev/null 2>&1; then - fail "${variant} environment port unexpectedly beat the CLI port" - fi - grep -F "127.0.0.1:${primary_port}" "${log}" >/dev/null \ - || fail "${variant} startup log did not identify the effective primary bind" - [ -s "${db}" ] || fail "${variant} SQLite database was not created" - stop_gateway "${pid}" - - echo "==> ${variant}: SQLite reopen and health-port zero override" - : >"${log}" - XDG_CONFIG_HOME="${dir}/config" \ - XDG_STATE_HOME="${dir}/state" \ - OPENSHELL_PODMAN_SOCKET="${PODMAN_SOCKET}" \ - "${gateway}" \ - --config "${config}" \ - --db-url "sqlite:${db}?mode=rwc" \ - --port "${second_primary}" \ - --health-port 0 >"${log}" 2>&1 & - pid=$! - PIDS=("${pid}") - wait_for_url "${pid}" "http://127.0.0.1:${metrics_port}/metrics" "${log}" \ - || fail "${variant} did not reopen its SQLite database" - if curl --noproxy '*' --max-time 1 -fsS "http://127.0.0.1:${health_port}/healthz" >/dev/null 2>&1; then - fail "${variant} health listener remained active after --health-port 0" - fi - stop_gateway "${pid}" - - echo "==> ${variant}: legacy singleton environment selector" - local legacy_config="${dir}/legacy-selector.toml" - grep -v '^compute_driver' "${config}" >"${legacy_config}" - : >"${log}" - env -u OPENSHELL_COMPUTE_DRIVER \ - XDG_CONFIG_HOME="${dir}/config" \ - XDG_STATE_HOME="${dir}/state" \ - OPENSHELL_PODMAN_SOCKET="${PODMAN_SOCKET}" \ - OPENSHELL_DRIVERS=podman \ - "${gateway}" \ - --config "${legacy_config}" \ - --db-url "sqlite:${db}?mode=rwc" \ - --port "${second_primary}" >"${log}" 2>&1 & - pid=$! - PIDS=("${pid}") - wait_for_url "${pid}" "http://127.0.0.1:${health_port}/healthz" "${log}" \ - || fail "${variant} did not accept the legacy singleton selector" - if [ "${variant}" = candidate ]; then - grep -F 'OPENSHELL_DRIVERS is deprecated' "${log}" >/dev/null \ - || fail "candidate did not emit the legacy selector deprecation warning" - fi - stop_gateway "${pid}" - - echo "==> ${variant}: database URL in TOML is rejected without value disclosure" - local invalid="${dir}/invalid.toml" secret="parity-secret-${variant}" - awk -v secret="${secret}" ' - /^name =/ { print "database_url = \"postgres://user:" secret "@127.0.0.1/db\"" } - { print } - ' "${config}" >"${invalid}" - if OPENSHELL_PODMAN_SOCKET="${PODMAN_SOCKET}" \ - "${gateway}" --config "${invalid}" >"${dir}/invalid.log" 2>&1; then - fail "${variant} accepted database_url in gateway TOML" - fi - grep -F 'database_url' "${dir}/invalid.log" >/dev/null \ - || fail "${variant} database rejection did not identify the field" - if grep -F "${secret}" "${dir}/invalid.log" >/dev/null; then - fail "${variant} database rejection disclosed the configured secret" - fi - - cat >"${RESULTS_DIR}/gateway-options-${variant}.json" <"${RESULTS_DIR}/gateway-options-comparison.json" <<'EOF' -{"profile":"gateway-options","baseline_success":true,"candidate_success":true,"parity":true} -EOF - -echo "Gateway option parity passed." diff --git a/e2e/parity/kubernetes-options-test.sh b/e2e/parity/kubernetes-options-test.sh deleted file mode 100644 index 4f14687e7a..0000000000 --- a/e2e/parity/kubernetes-options-test.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash - -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -set -euo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -SCRIPT="${ROOT}/e2e/parity/kubernetes-options.sh" -TMP="$(mktemp -d)" -trap 'rm -rf "${TMP}"' EXIT - -OPENSHELL_PARITY_HOST_GATEWAY_IP=169.254.1.2 bash "${SCRIPT}" --print-config baseline >"${TMP}/baseline.toml" -OPENSHELL_PARITY_HOST_GATEWAY_IP=169.254.1.2 bash "${SCRIPT}" --print-config candidate >"${TMP}/candidate.toml" -python3 -I - "${TMP}/baseline.toml" "${TMP}/candidate.toml" <<'PY' -import sys, tomllib -def check(condition, message): - if not condition: - raise RuntimeError(message) -baseline=tomllib.load(open(sys.argv[1],'rb'))['openshell'] -candidate=tomllib.load(open(sys.argv[2],'rb'))['openshell'] -check(baseline['version']==1 and candidate['version']==2,'schema versions differ') -check(baseline['gateway']['compute_drivers']==['kubernetes'],'baseline selector differs') -check(candidate['gateway']['compute_driver']=='kubernetes','candidate selector differs') -shared={'default_image','supervisor_image','client_tls_secret_name','service_account_name','host_gateway_ip','enable_user_namespaces','sa_token_ttl_secs'} -check(shared <= baseline['gateway'].keys(),'baseline inherited fields missing') -check(shared.isdisjoint(candidate['gateway'].keys()),'candidate leaked driver fields into gateway table') -check(shared <= candidate['drivers']['kubernetes'].keys(),'candidate driver fields missing') -b=dict(baseline['drivers']['kubernetes']); b.update({key:baseline['gateway'][key] for key in shared}) -c=dict(candidate['drivers']['kubernetes']) -for projection in (b,c): - projection['gateway_id']='' - projection['grpc_endpoint']='http://host.openshell.internal:' - for field in ('image_pull_policy','supervisor_image_pull_policy'): - projection[field]={'IfNotPresent':'if_not_present'}.get(projection[field],projection[field]) -check(b==c,'schema-independent Kubernetes option projections differ') -PY - -: >"${TMP}/kubeconfig" -set +e -OPENSHELL_PARITY_BASELINE_ROOT="${TMP}/not-a-worktree" \ -OPENSHELL_PARITY_BASELINE_GATEWAY=/bin/true \ -OPENSHELL_PARITY_CANDIDATE_GATEWAY=/bin/true \ -OPENSHELL_PARITY_CLI=/bin/true \ -OPENSHELL_PARITY_KUBECONFIG="${TMP}/kubeconfig" \ -OPENSHELL_PARITY_KUBE_CONTEXT=default/external-production-cluster \ -OPENSHELL_PARITY_HOST_GATEWAY_IP=169.254.1.2 \ -bash "${SCRIPT}" >"${TMP}/unsafe.out" 2>&1 -status=$? -set -e -[ "${status}" -ne 0 ] -grep -F 'refusing non-parity context' "${TMP}/unsafe.out" >/dev/null - -echo "Kubernetes option parity deterministic tests passed." diff --git a/e2e/parity/kubernetes-options.sh b/e2e/parity/kubernetes-options.sh deleted file mode 100644 index 3ddca0355e..0000000000 --- a/e2e/parity/kubernetes-options.sh +++ /dev/null @@ -1,432 +0,0 @@ -#!/usr/bin/env bash - -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Compare the frozen schema-v1 and current schema-v2 Kubernetes driver against -# one explicitly supplied, disposable kind cluster. Gateway processes run on -# the host so the same cluster and candidate-owned oracle exercise both schemas. - -set -euo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -BASELINE_SHA="${OPENSHELL_PARITY_BASELINE_SHA:-74960ebfaeec4673885089ed995fad902459749f}" -CANDIDATE_SHA="${OPENSHELL_PARITY_CANDIDATE_SHA:-$(git -C "${ROOT}" rev-parse HEAD)}" -BASELINE_ROOT="${OPENSHELL_PARITY_BASELINE_ROOT:-}" -BASELINE_GATEWAY="${OPENSHELL_PARITY_BASELINE_GATEWAY:-}" -CANDIDATE_GATEWAY="${OPENSHELL_PARITY_CANDIDATE_GATEWAY:-}" -CLI="${OPENSHELL_PARITY_CLI:-}" -ARTIFACT_MANIFEST="${OPENSHELL_PARITY_ARTIFACT_MANIFEST:-}" -KUBECONFIG_PATH="${OPENSHELL_PARITY_KUBECONFIG:-}" -KUBE_CONTEXT="${OPENSHELL_PARITY_KUBE_CONTEXT:-}" -HOST_GATEWAY_IP="${OPENSHELL_PARITY_HOST_GATEWAY_IP:-}" -RUN_ID="${OPENSHELL_PARITY_RUN_ID:-$(date +%s)-$$}" -OUT="${OPENSHELL_PARITY_OUTPUT_DIR:-${ROOT}/target/parity/step8-kubernetes-${CANDIDATE_SHA:0:8}}" -SANDBOX_IMAGE="${OPENSHELL_PARITY_KUBERNETES_SANDBOX_IMAGE:-nvcr.io/nvidia/base/ubuntu:24.04}" -SUPERVISOR_IMAGE="${OPENSHELL_PARITY_KUBERNETES_SUPERVISOR_IMAGE:-ghcr.io/nvidia/openshell/supervisor:latest}" -RUNTIME_CLASS="openshell-parity-runc-${RUN_ID}" - -fail() { - echo "ERROR: Kubernetes option parity: $*" >&2 - exit 1 -} - -kctl() { - kubectl --kubeconfig "${KUBECONFIG_PATH}" --context "${KUBE_CONTEXT}" "$@" -} - -pick_port() { - python3 -I - <<'PY' -import socket -with socket.socket() as sock: - sock.bind(("0.0.0.0", 0)) - print(sock.getsockname()[1]) -PY -} - -write_config() { - local variant=$1 - local path=$2 - local namespace=$3 - local port=$4 - local run_dir=$5 - local gateway_id="step8-${variant}-${RUN_ID}" - local pull_policy - - if [ "${variant}" = baseline ]; then - pull_policy=IfNotPresent - cat >"${path}" <"${path}" <}" ;; esac -[[ "${BASELINE_SHA}" =~ ^[0-9a-f]{40}$ ]] || fail "baseline SHA must be a full lowercase SHA-1" -[[ "${CANDIDATE_SHA}" =~ ^[0-9a-f]{40}$ ]] || fail "candidate SHA must be a full lowercase SHA-1" -[[ "${RUN_ID}" =~ ^[a-z0-9]([a-z0-9-]{0,30}[a-z0-9])?$ ]] || fail "run ID must be a lowercase DNS label of at most 32 characters" -[[ "${SANDBOX_IMAGE}" =~ ^[A-Za-z0-9][A-Za-z0-9._/:@+-]{0,254}$ ]] || fail "sandbox image contains unsafe characters" -[[ "${SUPERVISOR_IMAGE}" =~ ^[A-Za-z0-9][A-Za-z0-9._/:@+-]{0,254}$ ]] || fail "supervisor image contains unsafe characters" -python3 -I - "${HOST_GATEWAY_IP}" <<'PY' -import ipaddress, sys -value=ipaddress.ip_address(sys.argv[1]) -if value.version != 4: - raise SystemExit("host gateway IP must be IPv4") -PY -[ "$(git -C "${BASELINE_ROOT}" rev-parse HEAD)" = "${BASELINE_SHA}" ] || fail "baseline worktree is not ${BASELINE_SHA}" -[ "$(git -C "${ROOT}" rev-parse HEAD)" = "${CANDIDATE_SHA}" ] || fail "candidate worktree is not ${CANDIDATE_SHA}" -[ "$(kubectl --kubeconfig "${KUBECONFIG_PATH}" config current-context)" = "${KUBE_CONTEXT}" ] || fail "private kubeconfig current context differs from requested parity context" -[ "$(kctl -n kube-system get configmap openshell-parity-guard -o jsonpath='{.data.context}')" = "${KUBE_CONTEXT}" ] || fail "cluster lacks the matching provisioning-time parity guard" -[ "$(kctl -n kube-system get configmap openshell-parity-guard -o jsonpath='{.data.purpose}')" = schema-v2-capability-parity ] || fail "cluster parity guard has the wrong purpose" -kctl get nodes -o name | grep -q '^node/openshell-parity-' || fail "requested context is not the dedicated OpenShell parity cluster" -[ "$(kctl get crd sandboxes.agents.x-k8s.io -o jsonpath='{.status.conditions[?(@.type=="Established")].status}')" = True ] || fail "Agent Sandbox CRD is not established" -[ -f "${ARTIFACT_MANIFEST}" ] || fail "OPENSHELL_PARITY_ARTIFACT_MANIFEST is required" -python3 -I - "${ARTIFACT_MANIFEST}" "${BASELINE_SHA}" "${CANDIDATE_SHA}" "${BASELINE_GATEWAY}" "${CANDIDATE_GATEWAY}" "${CLI}" <<'PY' -import hashlib, pathlib, sys, tomllib -manifest=tomllib.load(open(sys.argv[1],'rb')) -expected={'baseline_commit':sys.argv[2],'candidate_commit':sys.argv[3]} -for key, value in expected.items(): - if manifest.get(key) != value: - raise SystemExit(f'artifact manifest {key} does not match') -for key, path in zip(('baseline_gateway_sha256','candidate_gateway_sha256','candidate_cli_sha256'),sys.argv[4:]): - digest=hashlib.sha256(pathlib.Path(path).read_bytes()).hexdigest() - if manifest.get(key) != digest: - raise SystemExit(f'artifact manifest {key} does not match supplied binary') -PY - -PARITY_ROOT="$(realpath -m "${ROOT}/target/parity")" -OUT="$(realpath -m "${OUT}")" -case "${OUT}" in "${PARITY_ROOT}"/step8-kubernetes-*) ;; *) fail "output must be a step8-kubernetes-* directory below ${PARITY_ROOT}" ;; esac -[ ! -L "${OUT}" ] || fail "output directory must not be a symlink" -rm -rf --one-file-system "${OUT}" -umask 077 -mkdir -p "${OUT}/raw" -cp "${ARTIFACT_MANIFEST}" "${OUT}/artifact-manifest.toml" -printf '%s\n' "${BASELINE_SHA}" >"${OUT}/baseline.sha" -printf '%s\n' "${CANDIDATE_SHA}" >"${OUT}/candidate.sha" -printf '%s\n' "${KUBE_CONTEXT}" >"${OUT}/context" - -runtime_class_created=false -cleanup_cluster_fixture() { - local status=$? - local cleanup_status=0 - set +e - if ${runtime_class_created}; then - kctl delete runtimeclass "${RUNTIME_CLASS}" --ignore-not-found --wait=true --timeout=120s >/dev/null 2>&1 - cleanup_status=$? - fi - set -e - if [ "${status}" -eq 0 ] && [ "${cleanup_status}" -ne 0 ]; then - echo "ERROR: failed to confirm RuntimeClass cleanup" >&2 - exit 1 - fi - exit "${status}" -} -trap cleanup_cluster_fixture EXIT -cat </dev/null -apiVersion: node.k8s.io/v1 -kind: RuntimeClass -metadata: - name: ${RUNTIME_CLASS} -handler: runc -EOF -runtime_class_created=true - -run_variant() ( - set -euo pipefail - local variant=$1 - local gateway=$2 - local namespace="openshell-parity-${variant}-${RUN_ID}" - local sandbox="k8s-${variant:0:1}-${RUN_ID: -6}" - local resource="default--${sandbox}" - local run_dir="${OUT}/raw/${variant}" - local config="${run_dir}/gateway.toml" - local port - local gateway_pid= - local registered_endpoint - local namespace_created=false - mkdir -p "${run_dir}/jwt" "${run_dir}/xdg-config/openshell/gateways/parity" "${run_dir}/xdg-state" "${run_dir}/xdg-data" - - cleanup_variant() { - local status=$? - local cleanup_status=0 - set +e - if [ -n "${gateway_pid}" ]; then - kill "${gateway_pid}" >/dev/null 2>&1 || true - wait "${gateway_pid}" >/dev/null 2>&1 || true - fi - rm -f "${run_dir}/jwt/signing.pem" "${run_dir}/client.key" - if ${namespace_created}; then - kctl delete namespace "${namespace}" --ignore-not-found --wait=true --timeout=120s >"${run_dir}/namespace-delete.log" 2>&1 - cleanup_status=$? - fi - set -e - if [ "${status}" -eq 0 ] && [ "${cleanup_status}" -ne 0 ]; then - echo "ERROR: ${variant} namespace cleanup was not confirmed" >&2 - exit 1 - fi - exit "${status}" - } - trap cleanup_variant EXIT - - openssl genpkey -algorithm ED25519 -out "${run_dir}/jwt/signing.pem" >/dev/null 2>&1 - openssl pkey -in "${run_dir}/jwt/signing.pem" -pubout -out "${run_dir}/jwt/public.pem" >/dev/null 2>&1 - printf 'step8-%s\n' "${variant}" >"${run_dir}/jwt/kid" - openssl req -x509 -newkey rsa:2048 -nodes -subj "/CN=step8-parity-client" \ - -keyout "${run_dir}/client.key" -out "${run_dir}/client.crt" -days 1 >/dev/null 2>&1 - - kctl create namespace "${namespace}" >"${run_dir}/namespace-create.log" - namespace_created=true - kctl -n "${namespace}" create serviceaccount parity-sandbox >"${run_dir}/service-account.log" - kctl -n "${namespace}" create secret generic parity-pull-secret \ - --type=kubernetes.io/dockerconfigjson --from-literal=.dockerconfigjson='{"auths":{}}' >"${run_dir}/pull-secret.log" - kctl -n "${namespace}" create secret generic parity-client-tls \ - --from-file=ca.crt="${run_dir}/client.crt" \ - --from-file=tls.crt="${run_dir}/client.crt" \ - --from-file=tls.key="${run_dir}/client.key" >"${run_dir}/client-tls-secret.log" - - port="$(pick_port)" - write_config "${variant}" "${config}" "${namespace}" "${port}" "${run_dir}" - KUBECONFIG="${KUBECONFIG_PATH}" \ - XDG_CONFIG_HOME="${run_dir}/xdg-config" XDG_STATE_HOME="${run_dir}/xdg-state" XDG_DATA_HOME="${run_dir}/xdg-data" \ - OPENSHELL_DB_URL="sqlite:${run_dir}/gateway.db" \ - "${gateway}" --config "${config}" >"${run_dir}/gateway.log" 2>&1 & - gateway_pid=$! - listener_ready=false - for _ in $(seq 1 60); do - if ! kill -0 "${gateway_pid}" >/dev/null 2>&1; then - fail "${variant} gateway exited before binding; see ${run_dir}/gateway.log" - fi - if python3 -I - "${port}" <<'PY' -import socket, sys -try: - with socket.create_connection(("127.0.0.1", int(sys.argv[1])), timeout=.2): - pass -except OSError: - raise SystemExit(1) -PY - then - listener_ready=true - break - fi - sleep 0.5 - done - ${listener_ready} || fail "${variant} gateway did not bind within 30 seconds" - - registered_endpoint="http://127.0.0.1:${port}" - cat >"${run_dir}/xdg-config/openshell/gateways/parity/metadata.json" <"${run_dir}/xdg-config/openshell/active_gateway" - - XDG_CONFIG_HOME="${run_dir}/xdg-config" XDG_STATE_HOME="${run_dir}/xdg-state" XDG_DATA_HOME="${run_dir}/xdg-data" \ - timeout 360 "${CLI}" sandbox create --name "${sandbox}" --cpu 250m --memory 128Mi --detach \ - >"${run_dir}/create.log" 2>&1 - XDG_CONFIG_HOME="${run_dir}/xdg-config" XDG_STATE_HOME="${run_dir}/xdg-state" XDG_DATA_HOME="${run_dir}/xdg-data" \ - timeout 60 "${CLI}" sandbox exec --name "${sandbox}" --no-tty -- \ - sh -c 'printf step8-kubernetes-exec' >"${run_dir}/exec.log" 2>&1 - grep -q 'step8-kubernetes-exec' "${run_dir}/exec.log" || fail "${variant} callback exec marker missing" - - kctl -n "${namespace}" get sandbox "${resource}" -o json >"${run_dir}/sandbox.json" - kctl -n "${namespace}" get pod "${resource}" -o json >"${run_dir}/pod.json" - kctl -n "${namespace}" get pvc "workspace-${resource}" -o json >"${run_dir}/pvc.json" - - python3 -I - "${run_dir}" "${SANDBOX_IMAGE}" "${SUPERVISOR_IMAGE}" "${HOST_GATEWAY_IP}" "${RUNTIME_CLASS}" <<'PY' -import json, pathlib, sys -def check(condition, message): - if not condition: - raise RuntimeError(message) -run=pathlib.Path(sys.argv[1]); sandbox_image, supervisor_image, host_ip, runtime_class=sys.argv[2:] -pod=json.loads((run/'pod.json').read_text()); pvc=json.loads((run/'pvc.json').read_text()); sb=json.loads((run/'sandbox.json').read_text()) -spec=pod['spec']; agent=next(c for c in spec['containers'] if c['name']=='agent'); env={x['name']:x.get('value','') for x in agent.get('env',[])} -inits={c['name']:c for c in spec.get('initContainers',[])}; install=inits['openshell-supervisor-install'] -vols={v['name']:v for v in spec.get('volumes',[])}; mounts={m['name']:m for m in agent.get('volumeMounts',[])} -hosts={(h, a['ip']) for a in spec.get('hostAliases',[]) for h in a.get('hostnames',[])} -check(pod['status']['phase']=='Running','Pod is not Running') -conditions={c['type']:c['status'] for c in sb.get('status',{}).get('conditions',[])} -check(conditions.get('Ready')=='True','Sandbox Ready condition is not true') -check(agent['image']==sandbox_image and agent['imagePullPolicy']=='IfNotPresent','sandbox image or pull policy differs') -check(install['image']==supervisor_image and install['imagePullPolicy']=='IfNotPresent','supervisor image or pull policy differs') -check([x['name'] for x in spec.get('imagePullSecrets',[])]==['parity-pull-secret'],'image pull Secret differs') -check(spec['serviceAccountName']=='parity-sandbox','ServiceAccount differs') -check(env['OPENSHELL_ENDPOINT'].startswith('http://host.openshell.internal:'),'callback endpoint differs') -check(env['OPENSHELL_SSH_SOCKET_PATH']=='/run/openshell/parity-kubernetes-ssh.sock','SSH socket differs') -check(env['OPENSHELL_SANDBOX_UID']=='1000' and env['OPENSHELL_SANDBOX_GID']=='1000','sandbox identity differs') -check(('host.openshell.internal',host_ip) in hosts and ('host.docker.internal',host_ip) in hosts,'host aliases differ') -check(spec['runtimeClassName']==runtime_class and spec.get('hostUsers',True) is not False,'RuntimeClass or user namespace posture differs') -check(agent['securityContext']['appArmorProfile']['type']=='Unconfined','AppArmor profile differs') -check(agent['resources']['requests']=={'cpu':'250m','memory':'128Mi'},'resource requests differ') -check(agent['resources']['limits']=={'cpu':'250m','memory':'128Mi'},'resource limits differ') -check(vols['openshell-sa-token']['projected']['sources'][0]['serviceAccountToken']['expirationSeconds']==600,'ServiceAccount token TTL differs') -check(vols['openshell-client-tls']['secret']['secretName']=='parity-client-tls','client TLS Secret differs') -check(mounts['openshell-client-tls']['readOnly'] is True,'client TLS mount is not read-only') -check(pvc['status']['phase']=='Bound' and pvc['spec']['storageClassName']=='standard','PVC phase or StorageClass differs') -check(pvc['spec']['resources']['requests']['storage']=='64Mi','PVC storage request differs') -labels=sb['metadata']['labels'] -for key in ('openshell.ai/sandbox-id','openshell.ai/sandbox-name','openshell.ai/sandbox-workspace','openshell.ai/gateway-id','openshell.ai/managed-by'): - check(labels.get(key),f'managed label {key} missing') -observed_sideload='init-container' if 'openshell-supervisor-install' in inits else 'unknown' -observed_topology='combined' if [c['name'] for c in spec['containers']]==['agent'] else 'other' -observed_workspace_mode='shared' if pvc['metadata']['namespace']==pod['metadata']['namespace'] and pvc['metadata']['name'].startswith('workspace-default--') else 'other' -check(observed_sideload=='init-container','supervisor sideload method differs') -check(observed_topology=='combined','supervisor topology differs') -check(observed_workspace_mode=='shared','workspace placement differs') -normalized={ - 'scenario':'kubernetes-core-options','pod_phase':'Running','sandbox_ready':True, - 'sandbox_image':agent['image'],'sandbox_image_pull_policy':agent['imagePullPolicy'], - 'image_pull_secrets':['parity-pull-secret'],'service_account':'parity-sandbox', - 'supervisor_image':install['image'],'supervisor_image_pull_policy':install['imagePullPolicy'], - 'supervisor_sideload_method':observed_sideload,'topology':observed_topology, - 'callback_endpoint_host':'host.openshell.internal','callback_exec':True, - 'ssh_socket_path':env['OPENSHELL_SSH_SOCKET_PATH'],'client_tls_secret':'parity-client-tls', - 'host_gateway_ip':host_ip,'sa_token_ttl_secs':600,'runtime_class_handler':'runc', - 'enable_user_namespaces':False,'app_armor_profile':'Unconfined','sandbox_uid':1000,'sandbox_gid':1000, - 'workspace_mode':observed_workspace_mode,'workspace_storage':'64Mi','workspace_storage_class':'standard','pvc_phase':'Bound', - 'cpu':'250m','memory':'128Mi','managed_labels':True, -} -(run.parent.parent/f'{run.name}.normalized.json').write_text(json.dumps(normalized,sort_keys=True,separators=(',',':'))+'\n') -PY - - XDG_CONFIG_HOME="${run_dir}/xdg-config" XDG_STATE_HOME="${run_dir}/xdg-state" XDG_DATA_HOME="${run_dir}/xdg-data" \ - timeout 60 "${CLI}" sandbox delete "${sandbox}" >"${run_dir}/delete.log" 2>&1 - for _ in $(seq 1 60); do - if ! kctl -n "${namespace}" get sandbox "${resource}" >/dev/null 2>&1 \ - && ! kctl -n "${namespace}" get pod "${resource}" >/dev/null 2>&1 \ - && ! kctl -n "${namespace}" get pvc "workspace-${resource}" >/dev/null 2>&1; then - break - fi - sleep 1 - done - ! kctl -n "${namespace}" get sandbox "${resource}" >/dev/null 2>&1 || fail "${variant} Sandbox remained after delete" - ! kctl -n "${namespace}" get pod "${resource}" >/dev/null 2>&1 || fail "${variant} Pod remained after delete" - ! kctl -n "${namespace}" get pvc "workspace-${resource}" >/dev/null 2>&1 || fail "${variant} PVC remained after delete" -) - -set +e -run_variant baseline "${BASELINE_GATEWAY}" -baseline_status=$? -run_variant candidate "${CANDIDATE_GATEWAY}" -candidate_status=$? -set -e - -baseline_success=false; candidate_success=false -[ "${baseline_status}" -eq 0 ] && baseline_success=true -[ "${candidate_status}" -eq 0 ] && candidate_success=true -parity=false; classification=regression; accepted=false -if ${baseline_success} && ${candidate_success} && [ -f "${OUT}/baseline.normalized.json" ] && [ -f "${OUT}/candidate.normalized.json" ]; then - if cmp -s "${OUT}/baseline.normalized.json" "${OUT}/candidate.normalized.json"; then - parity=true; classification=pass; accepted=true - fi -fi -cat >"${OUT}/comparison.json" <&2; exit 2 ;; -esac - -fail() { echo "ERROR: podman-options oracle: $*" >&2; exit 1; } -json_escape() { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'; } -podman_cmd() { - if [ "${OPENSHELL_E2E_CONTAINER_ENGINE_UNSET_XDG_CONFIG_HOME:-0}" = 1 ]; then - env -u XDG_CONFIG_HOME podman --url "unix://${OPENSHELL_PODMAN_SOCKET}" "$@" - elif [ -n "${OPENSHELL_E2E_CONTAINER_ENGINE_XDG_CONFIG_HOME:-}" ]; then - XDG_CONFIG_HOME="${OPENSHELL_E2E_CONTAINER_ENGINE_XDG_CONFIG_HOME}" podman --url "unix://${OPENSHELL_PODMAN_SOCKET}" "$@" - else - podman --url "unix://${OPENSHELL_PODMAN_SOCKET}" "$@" - fi -} -cleanup() { - status=$? - if [ "${CREATED}" = 1 ]; then "${CLI}" sandbox delete "${NAME}" >/dev/null 2>&1 || true; fi - rm -rf "${WORKDIR}" - exit "${status}" -} -trap cleanup EXIT - -mkdir -p "${WORKDIR}/bind-source" -printf '%s\n' parity-bind-mount >"${WORKDIR}/bind-source/probe" -bind_source="$(json_escape "${WORKDIR}/bind-source")" -DRIVER_CONFIG="{\"podman\":{\"mounts\":[{\"type\":\"bind\",\"source\":\"${bind_source}\",\"target\":\"/tmp/parity-bind\",\"read_only\":true,\"selinux_label\":\"private\"},{\"type\":\"tmpfs\",\"target\":\"/tmp/parity-cache\",\"options\":[\"nosuid\",\"nodev\"],\"size_bytes\":1048576,\"mode\":448}]}}" -"${CLI}" sandbox create --name "${NAME}" --cpu 750m --memory 384Mi \ - --driver-config-json "${DRIVER_CONFIG}" --detach -CREATED=1 -podman_cmd ps -aq --filter label=openshell.managed=true --filter "label=openshell.ai/sandbox-name=${NAME}" > "${WORKDIR}/ids" -env wc -l "${WORKDIR}/ids" | env grep -E "^[[:space:]]*1[[:space:]]" >/dev/null || fail "expected exactly one managed container" - -# Image IDs, names, and inspect attributes are checked but never emitted. -while IFS= read -r id; do - podman_cmd image inspect --format "{{.Id}}" "${IMAGE}" | env sed "s/^sha256://" > "${WORKDIR}/expected-image" - podman_cmd inspect --format "{{.Image}}" "${id}" | env sed "s/^sha256://" > "${WORKDIR}/actual-image" - env cmp -s "${WORKDIR}/expected-image" "${WORKDIR}/actual-image" || fail "selected sandbox image ID differs" - podman_cmd inspect --format "{{index .Config.Labels \"openshell.managed\"}}" "${id}" | env grep -Fx true >/dev/null || fail "managed label missing" - podman_cmd inspect --format "{{index .Config.Labels \"openshell.ai/sandbox-name\"}}" "${id}" | env grep -Fx "${NAME}" >/dev/null || fail "sandbox name label missing" - for label in openshell.ai/sandbox-id openshell.ai/sandbox-workspace; do - podman_cmd inspect --format "{{index .Config.Labels \"${label}\"}}" "${id}" | env grep -Ev "^(|)$" >/dev/null || fail "${label} missing" - done - actual_pids_limit="$(podman_cmd inspect --format "{{.HostConfig.PidsLimit}}" "${id}")" - [ "${actual_pids_limit}" = "${EXPECTED_PIDS_LIMIT}" ] || fail "pids limit is ${actual_pids_limit}, expected ${EXPECTED_PIDS_LIMIT}" - podman_cmd inspect --format "{{.HostConfig.CpuQuota}}" "${id}" | env grep -Fx 75000 >/dev/null || fail "CPU quota is not 750m" - podman_cmd inspect --format "{{.HostConfig.CpuPeriod}}" "${id}" | env grep -Fx 100000 >/dev/null || fail "CPU period is not 100000" - podman_cmd inspect --format "{{.HostConfig.Memory}}" "${id}" | env grep -Fx 402653184 >/dev/null || fail "memory limit is not 384Mi" - podman_cmd inspect --format '{{range .Mounts}}{{if eq .Destination "/tmp/parity-bind"}}{{.RW}}{{end}}{{end}}' "${id}" \ - | env grep -Fx false >/dev/null || fail "bind mount is not read-only" - podman_cmd inspect --format "{{.Config.Entrypoint}}" "${id}" | env grep -F /opt/openshell/bin/openshell-sandbox >/dev/null || fail "supervisor entrypoint missing" - podman_cmd inspect --format "{{index .Config.Cmd 0}} {{index .Config.Cmd 1}}" "${id}" | env grep -Fx -- "--workdir /sandbox" >/dev/null || fail "supervisor workdir differs" - podman_cmd inspect --format "{{range .Config.Env}}{{println .}}{{end}}" "${id}" | env grep -Fx OPENSHELL_SSH_SOCKET_PATH=/run/openshell/parity-ssh.sock >/dev/null || fail "SSH environment differs" - podman_cmd inspect --format "{{range .Config.Env}}{{println .}}{{end}}" "${id}" | env grep -E "^OPENSHELL_ENDPOINT=https://host\.containers\.internal:" >/dev/null || fail "callback endpoint differs" -done < "${WORKDIR}/ids" -# Both schema spellings must map to Podman's pull-if-missing request. Inspect -# the driver emission so regressions to always or never do not pass merely -# because the wrapper preloaded the image. -sed $'s/\033\[[0-9;]*m//g' "${GATEWAY_LOG}" \ - | env grep -F 'Ensuring sandbox image' \ - | env grep -F 'policy=missing' >/dev/null \ - || fail "image pull policy did not map to Podman missing" - -# Podman 5.8 reports Healthcheck.Interval in nanoseconds; wait for the -# eventual state instead of accepting a merely running container. -healthy=0 -for attempt in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90; do - while IFS= read -r id; do - podman_cmd inspect --format "{{.Config.Healthcheck.Interval}}" "${id}" | env grep -E "^(7000000000|7s)$" >/dev/null || fail "health interval is not 7 seconds" - if podman_cmd inspect --format "{{.State.Health.Status}}" "${id}" | env grep -Fx healthy >/dev/null; then healthy=1; fi - done < "${WORKDIR}/ids" - [ "${healthy}" = 1 ] && break - sleep 1 -done -[ "${healthy}" = 1 ] || fail "container did not become healthy" -container_id="$(cat "${WORKDIR}/ids")" -podman_cmd exec "${container_id}" sh -c 'test "$(cat /tmp/parity-bind/probe)" = parity-bind-mount' \ - || fail "read-only bind mount is unavailable" -podman_cmd exec "${container_id}" test -d /tmp/parity-cache \ - || fail "tmpfs mount is unavailable" -podman_cmd exec "${container_id}" test -s /etc/openshell/auth/sandbox.jwt \ - || fail "sandbox token mount is unavailable" -for tls_file in ca.crt tls.crt tls.key; do - podman_cmd exec "${container_id}" test -s "/etc/openshell/tls/client/${tls_file}" \ - || fail "guest TLS mount ${tls_file} is unavailable" -done -"${CLI}" sandbox exec --name "${NAME}" --no-tty --no-login-shell -- true - -# This is the normalized result: no container IDs, timestamps, IPs, or ports. -escaped_image="$(json_escape "${IMAGE}")" -printf "%s\n" "{\"scenario\":\"podman-options\",\"sandbox_image\":\"${escaped_image}\",\"image_pull_policy\":\"if_not_present\",\"managed_labels\":true,\"supervisor_entrypoint\":\"/opt/openshell/bin/openshell-sandbox\",\"supervisor_workdir\":\"/sandbox\",\"callback_endpoint_scheme\":\"https\",\"callback_endpoint_host\":\"host.containers.internal\",\"ssh_socket_path\":\"/run/openshell/parity-ssh.sock\",\"cpu_millis\":750,\"memory_bytes\":402653184,\"pids_limit\":${actual_pids_limit},\"bind_mount\":\"read_only\",\"tmpfs_mount\":true,\"sandbox_token_mount\":true,\"guest_tls_mounts\":true,\"health_check_interval_secs\":7,\"health\":\"healthy\",\"callback_exec\":true}" > "${RESULT}" diff --git a/e2e/parity/run.sh b/e2e/parity/run.sh deleted file mode 100755 index d9721658a9..0000000000 --- a/e2e/parity/run.sh +++ /dev/null @@ -1,658 +0,0 @@ -#!/usr/bin/env bash -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Compare the frozen schema-v1 gateway contract with the checkout's schema-v2 -# contract. This intentionally begins with one small semantic scenario; later -# parity waves add scenarios without changing the isolated variant runner. - -set -euo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -MANIFEST="${OPENSHELL_PARITY_CAPABILITY_MANIFEST:-${ROOT}/e2e/configs/gateway/schema-v2-capability-parity.toml}" -DRIVER="" -SCENARIO="smoke" -COMMAND_CLASS="conformance_smoke" -BASELINE_WORKTREE="${OPENSHELL_PARITY_BASELINE_WORKTREE:-}" -CANDIDATE_WORKTREE="${OPENSHELL_PARITY_CANDIDATE_WORKTREE:-${ROOT}}" -RESULTS_DIR="${OPENSHELL_PARITY_RESULTS_DIR:-}" -WRAPPER="${OPENSHELL_PARITY_PODMAN_WRAPPER:-${ROOT}/e2e/with-podman-gateway.sh}" -PODMAN_OPTIONS_ORACLE="${OPENSHELL_PARITY_PODMAN_OPTIONS_ORACLE:-${ROOT}/e2e/parity/podman-options.sh}" -CONFORMANCE_TRACE_WRAPPER="${ROOT}/e2e/parity/trace-conformance.sh" -RESULTS_VERIFIER="${ROOT}/e2e/parity/verify-results.py" -PODMAN_BIN="${OPENSHELL_PARITY_PODMAN_BIN:-podman}" -DEFAULT_SANDBOX_IMAGE="nvcr.io/nvidia/base/ubuntu:24.04" -SANDBOX_IMAGE_REQUEST="${OPENSHELL_E2E_PODMAN_SANDBOX_IMAGE:-${DEFAULT_SANDBOX_IMAGE}}" -PARITY_SANDBOX_RUNTIME_IMAGE="" -PARITY_SUPERVISOR_BASE_IMAGE="" -PARITY_SUPERVISOR_BASE_RUNTIME_IMAGE="" -TEMP_WORKTREE="" -RUN_DIR="" - -usage() { - cat >&2 <&2; exit 2; } - DRIVER=$2 - shift 2 - ;; - --scenario) - [ "$#" -ge 2 ] || { echo "ERROR: --scenario requires a value." >&2; exit 2; } - SCENARIO=$2 - shift 2 - ;; - --baseline-worktree) - [ "$#" -ge 2 ] || { echo "ERROR: --baseline-worktree requires a path." >&2; exit 2; } - BASELINE_WORKTREE=$2 - shift 2 - ;; - --results-dir) - [ "$#" -ge 2 ] || { echo "ERROR: --results-dir requires a path." >&2; exit 2; } - RESULTS_DIR=$2 - shift 2 - ;; - -h|--help) usage; exit 0 ;; - *) echo "ERROR: unknown option: $1" >&2; usage; exit 2 ;; - esac -done - -case "${SCENARIO}" in - smoke) COMMAND_CLASS="conformance_smoke" ;; - external-driver) COMMAND_CLASS="external_driver_conformance_smoke" ;; - podman-options) COMMAND_CLASS="podman_options" ;; - *) echo "ERROR: unsupported parity scenario: ${SCENARIO}." >&2; exit 2 ;; -esac - -if [ "${DRIVER}" != "podman" ]; then - echo "ERROR: only --driver podman is supported by the schema parity harness (got ${DRIVER:-})." >&2 - echo " Docker, Kubernetes, and VM backends are reserved for later parity waves." >&2 - exit 2 -fi - -if [ ! -f "${MANIFEST}" ]; then - echo "ERROR: parity capability manifest not found: ${MANIFEST}" >&2 - exit 2 -fi -BASELINE_SHA="$(awk -F '"' '/^[[:space:]]*baseline_commit[[:space:]]*=/ { print $2; exit }' "${MANIFEST}")" -if ! [[ "${BASELINE_SHA}" =~ ^[0-9a-fA-F]{40}$ ]]; then - echo "ERROR: manifest baseline_commit must be a full 40-character SHA: ${MANIFEST}" >&2 - exit 2 -fi -BASELINE_SHA="$(echo "$BASELINE_SHA" | tr '[:upper:]' '[:lower:]')" -EXPECTED_CANDIDATE_SHA="$(git -C "${ROOT}" rev-parse HEAD)" -if [ ! -d "${CANDIDATE_WORKTREE}" ]; then - echo "ERROR: candidate worktree does not exist: ${CANDIDATE_WORKTREE}" >&2 - exit 2 -fi -CANDIDATE_SHA="$(git -C "${CANDIDATE_WORKTREE}" rev-parse HEAD 2>/dev/null || true)" -if [ "${CANDIDATE_SHA}" != "${EXPECTED_CANDIDATE_SHA}" ]; then - echo "ERROR: candidate worktree must be at current commit ${EXPECTED_CANDIDATE_SHA}: ${CANDIDATE_WORKTREE}" >&2 - exit 2 -fi - -cleanup() { - local status=$? - if [ -n "${TEMP_WORKTREE}" ]; then - git -C "${ROOT}" worktree remove --force "${TEMP_WORKTREE}" >/dev/null 2>&1 || true - fi - if [ -n "${RUN_DIR}" ]; then - # Rootless Podman overlay files can be owned by subordinate UIDs. Remove an - # isolated container store from Podman's user namespace before falling back - # to ordinary cleanup for runs that never reached the container runtime. - if { [ -d "${RUN_DIR}/baseline/data/containers/storage" ] \ - || [ -d "${RUN_DIR}/candidate/data/containers/storage" ] \ - || [ -d "${RUN_DIR}/sandbox-resolver/data/containers/storage" ]; } \ - && command -v "${PODMAN_BIN}" >/dev/null 2>&1; then - "${PODMAN_BIN}" unshare rm -rf -- "${RUN_DIR}" >/dev/null 2>&1 || true - fi - rm -rf "${RUN_DIR}" >/dev/null 2>&1 || true - fi - exit "${status}" -} -trap cleanup EXIT - -if [ -n "${BASELINE_WORKTREE}" ]; then - if [ ! -d "${BASELINE_WORKTREE}" ]; then - echo "ERROR: baseline worktree does not exist: ${BASELINE_WORKTREE}" >&2 - exit 2 - fi - resolved_baseline_sha="$(git -C "${BASELINE_WORKTREE}" rev-parse HEAD 2>/dev/null || true)" - if [ "${resolved_baseline_sha}" != "${BASELINE_SHA}" ]; then - echo "ERROR: baseline worktree is not frozen manifest commit ${BASELINE_SHA}: ${BASELINE_WORKTREE}" >&2 - exit 2 - fi -else - if ! git -C "${ROOT}" cat-file -e "${BASELINE_SHA}^{commit}" 2>/dev/null; then - echo "ERROR: frozen baseline ${BASELINE_SHA} is unavailable locally; fetch it before running parity." >&2 - exit 2 - fi - TEMP_WORKTREE="$(mktemp -d "${TMPDIR:-/tmp}/openshell-parity-baseline.XXXXXX")" - # Remove mktemp's directory so git worktree can create and register it. - rmdir "${TEMP_WORKTREE}" - git -C "${ROOT}" worktree add --detach "${TEMP_WORKTREE}" "${BASELINE_SHA}" >/dev/null - BASELINE_WORKTREE="${TEMP_WORKTREE}" -fi - -require_clean_source() { - local variant=$1 source_root=$2 dirty - dirty="$(git -C "${source_root}" status --porcelain=v1 --untracked-files=all)" - if [ -n "${dirty}" ]; then - echo "ERROR: ${variant} source worktree must be clean before building parity artifacts: ${source_root}" >&2 - printf '%s\n' "${dirty}" >&2 - exit 2 - fi -} - -RUN_DIR="$(mktemp -d "${TMPDIR:-/tmp}/openshell-parity-run.XXXXXX")" -if [ -n "${RESULTS_DIR}" ]; then - if [ -e "${RESULTS_DIR}" ] || [ -L "${RESULTS_DIR}" ]; then - echo "ERROR: parity results directory already exists; choose a fresh path: ${RESULTS_DIR}" >&2 - exit 2 - fi - mkdir -p "$(dirname "${RESULTS_DIR}")" - mkdir -m 0700 "${RESULTS_DIR}" -else - mkdir -p "${ROOT}/target/parity" - RESULTS_DIR="$(mktemp -d "${ROOT}/target/parity/run.${SCENARIO}.XXXXXX")" -fi -RESULTS_DIR="$(cd "${RESULTS_DIR}" && pwd)" -echo "Retaining parity evidence in ${RESULTS_DIR}" - -require_executable() { - local label=$1 - local binary=$2 - if [ ! -x "${binary}" ]; then - echo "ERROR: ${label} binary is not executable: ${binary}" >&2 - exit 2 - fi -} - -build_variant() { - local variant=$1 source_root=$2 target_dir=$3 gateway_override=$4 cli_override=$5 conformance_override=$6 - local gateway_var=$7 cli_var=$8 conformance_var=$9 - local gateway cli conformance jobs=() gateway_features=() - - if [ -n "${CARGO_BUILD_JOBS:-}" ]; then jobs=(-j "${CARGO_BUILD_JOBS}"); fi - if [ "${SCENARIO}" = external-driver ]; then - gateway_features=(--no-default-features --features telemetry) - fi - target_dir="${target_dir:-${ROOT}/target/parity/${variant}}" - case "${target_dir}" in /*) ;; *) target_dir="${ROOT}/${target_dir}" ;; esac - gateway="${gateway_override:-${target_dir}/debug/openshell-gateway}" - cli="${cli_override:-${target_dir}/debug/openshell}" - conformance="${conformance_override:-${target_dir}/debug/openshell-conformance}" - - if [ -z "${gateway_override}" ]; then - require_clean_source "${variant}" "${source_root}" - echo "Building ${variant} gateway in ${target_dir}..." - (cd "${source_root}" && CARGO_TARGET_DIR="${target_dir}" cargo build "${jobs[@]}" -p openshell-gateway --bin openshell-gateway "${gateway_features[@]}") - fi - if [ -z "${cli_override}" ]; then - require_clean_source "${variant}" "${source_root}" - echo "Building ${variant} CLI in ${target_dir}..." - (cd "${source_root}" && CARGO_TARGET_DIR="${target_dir}" cargo build "${jobs[@]}" -p openshell-cli) - fi - if [ -z "${conformance_override}" ]; then - require_clean_source "${variant}" "${source_root}" - echo "Building ${variant} conformance CLI in ${target_dir}..." - (cd "${source_root}" && CARGO_TARGET_DIR="${target_dir}" cargo build "${jobs[@]}" -p openshell-conformance-cli) - fi - require_executable "${variant} gateway" "${gateway}" - require_executable "${variant} CLI" "${cli}" - require_executable "${variant} conformance" "${conformance}" - printf -v "${gateway_var}" '%s' "${gateway}" - printf -v "${cli_var}" '%s' "${cli}" - printf -v "${conformance_var}" '%s' "${conformance}" -} - -BASELINE_GATEWAY="" BASELINE_CLI="" BASELINE_CONFORMANCE="" -CANDIDATE_GATEWAY="" CANDIDATE_CLI="" CANDIDATE_CONFORMANCE="" -build_variant baseline "${BASELINE_WORKTREE}" "${OPENSHELL_PARITY_BASELINE_CARGO_TARGET_DIR:-}" "${OPENSHELL_PARITY_BASELINE_GATEWAY_BIN:-}" "${OPENSHELL_PARITY_BASELINE_CLI_BIN:-}" "${OPENSHELL_PARITY_BASELINE_CONFORMANCE_BIN:-}" BASELINE_GATEWAY BASELINE_CLI BASELINE_CONFORMANCE -build_variant candidate "${CANDIDATE_WORKTREE}" "${OPENSHELL_PARITY_CANDIDATE_CARGO_TARGET_DIR:-}" "${OPENSHELL_PARITY_CANDIDATE_GATEWAY_BIN:-}" "${OPENSHELL_PARITY_CANDIDATE_CLI_BIN:-}" "${OPENSHELL_PARITY_CANDIDATE_CONFORMANCE_BIN:-}" CANDIDATE_GATEWAY CANDIDATE_CLI CANDIDATE_CONFORMANCE - -BASELINE_GATEWAY_ORIGIN=built_by_harness -BASELINE_CLI_ORIGIN=built_by_harness -BASELINE_CONFORMANCE_ORIGIN=built_by_harness -CANDIDATE_GATEWAY_ORIGIN=built_by_harness -CANDIDATE_CLI_ORIGIN=built_by_harness -CANDIDATE_CONFORMANCE_ORIGIN=built_by_harness -[ -z "${OPENSHELL_PARITY_BASELINE_GATEWAY_BIN:-}" ] || BASELINE_GATEWAY_ORIGIN=supplied_override -[ -z "${OPENSHELL_PARITY_BASELINE_CLI_BIN:-}" ] || BASELINE_CLI_ORIGIN=supplied_override -[ -z "${OPENSHELL_PARITY_BASELINE_CONFORMANCE_BIN:-}" ] || BASELINE_CONFORMANCE_ORIGIN=supplied_override -[ -z "${OPENSHELL_PARITY_CANDIDATE_GATEWAY_BIN:-}" ] || CANDIDATE_GATEWAY_ORIGIN=supplied_override -[ -z "${OPENSHELL_PARITY_CANDIDATE_CLI_BIN:-}" ] || CANDIDATE_CLI_ORIGIN=supplied_override -[ -z "${OPENSHELL_PARITY_CANDIDATE_CONFORMANCE_BIN:-}" ] || CANDIDATE_CONFORMANCE_ORIGIN=supplied_override - -build_external_driver() { - local variant=$1 source_root=$2 target_dir=$3 override=$4 output_var=$5 - local binary - if [ "${SCENARIO}" != external-driver ]; then - printf -v "${output_var}" '%s' "" - return - fi - target_dir="${target_dir:-${ROOT}/target/parity/${variant}}" - case "${target_dir}" in /*) ;; *) target_dir="${ROOT}/${target_dir}" ;; esac - binary="${override:-${target_dir}/debug/openshell-driver-podman}" - if [ -z "${override}" ]; then - require_clean_source "${variant}" "${source_root}" - echo "Building ${variant} external Podman driver in ${target_dir}..." - (cd "${source_root}" && CARGO_TARGET_DIR="${target_dir}" cargo build -p openshell-driver-podman --bin openshell-driver-podman) - fi - require_executable "${variant} external Podman driver" "${binary}" - printf -v "${output_var}" '%s' "${binary}" -} - -BASELINE_EXTERNAL_DRIVER="" CANDIDATE_EXTERNAL_DRIVER="" -BASELINE_EXTERNAL_DRIVER_ORIGIN=not_applicable -CANDIDATE_EXTERNAL_DRIVER_ORIGIN=not_applicable -if [ "${SCENARIO}" = external-driver ]; then - BASELINE_EXTERNAL_DRIVER_ORIGIN=built_by_harness - CANDIDATE_EXTERNAL_DRIVER_ORIGIN=built_by_harness - [ -z "${OPENSHELL_PARITY_BASELINE_EXTERNAL_DRIVER_BIN:-}" ] || BASELINE_EXTERNAL_DRIVER_ORIGIN=supplied_override - [ -z "${OPENSHELL_PARITY_CANDIDATE_EXTERNAL_DRIVER_BIN:-}" ] || CANDIDATE_EXTERNAL_DRIVER_ORIGIN=supplied_override -fi -build_external_driver baseline "${BASELINE_WORKTREE}" "${OPENSHELL_PARITY_BASELINE_CARGO_TARGET_DIR:-}" "${OPENSHELL_PARITY_BASELINE_EXTERNAL_DRIVER_BIN:-}" BASELINE_EXTERNAL_DRIVER -build_external_driver candidate "${CANDIDATE_WORKTREE}" "${OPENSHELL_PARITY_CANDIDATE_CARGO_TARGET_DIR:-}" "${OPENSHELL_PARITY_CANDIDATE_EXTERNAL_DRIVER_BIN:-}" CANDIDATE_EXTERNAL_DRIVER -if [ "${SCENARIO}" = external-driver ]; then - baseline_external_realpath="$(realpath "${BASELINE_EXTERNAL_DRIVER}")" - candidate_external_realpath="$(realpath "${CANDIDATE_EXTERNAL_DRIVER}")" - if [ "${baseline_external_realpath}" = "${candidate_external_realpath}" ] \ - || [ "${BASELINE_EXTERNAL_DRIVER}" -ef "${CANDIDATE_EXTERNAL_DRIVER}" ]; then - echo "ERROR: external-driver parity requires distinct baseline and candidate driver artifacts." >&2 - exit 2 - fi -fi - -supervisor_target_triple() { - case "$(uname -sm)" in - "Linux x86_64") printf '%s\n' x86_64-unknown-linux-musl ;; - "Linux aarch64"|"Linux arm64") printf '%s\n' aarch64-unknown-linux-musl ;; - *) echo "ERROR: Podman parity supervisor builds require Linux x86_64 or arm64." >&2; return 2 ;; - esac -} - -build_supervisor() { - local variant=$1 source_root=$2 target_dir=$3 override=$4 output_var=$5 - local binary target jobs=() - target_dir="${target_dir:-${ROOT}/target/parity/${variant}}" - case "${target_dir}" in /*) ;; *) target_dir="${ROOT}/${target_dir}" ;; esac - if [ -n "${override}" ]; then - binary="${override}" - else - target="$(supervisor_target_triple)" - binary="${target_dir}/${target}/release/openshell-sandbox" - require_clean_source "${variant}" "${source_root}" - if [ -n "${CARGO_BUILD_JOBS:-}" ]; then jobs=(-j "${CARGO_BUILD_JOBS}"); fi - echo "Building ${variant} supervisor in ${target_dir}..." - (cd "${source_root}" && CARGO_TARGET_DIR="${target_dir}" cargo build "${jobs[@]}" --release --target "${target}" -p openshell-sandbox --bin openshell-sandbox) - "${source_root}/tasks/scripts/verify-static-binary.sh" "${binary}" - fi - require_executable "${variant} supervisor" "${binary}" - printf -v "${output_var}" '%s' "${binary}" -} - -BASELINE_SUPERVISOR="" CANDIDATE_SUPERVISOR="" -BASELINE_SUPERVISOR_ORIGIN=built_by_harness -CANDIDATE_SUPERVISOR_ORIGIN=built_by_harness -[ -z "${OPENSHELL_PARITY_BASELINE_SUPERVISOR_BIN:-}" ] || BASELINE_SUPERVISOR_ORIGIN=supplied_override -[ -z "${OPENSHELL_PARITY_CANDIDATE_SUPERVISOR_BIN:-}" ] || CANDIDATE_SUPERVISOR_ORIGIN=supplied_override -build_supervisor baseline "${BASELINE_WORKTREE}" "${OPENSHELL_PARITY_BASELINE_CARGO_TARGET_DIR:-}" "${OPENSHELL_PARITY_BASELINE_SUPERVISOR_BIN:-}" BASELINE_SUPERVISOR -build_supervisor candidate "${CANDIDATE_WORKTREE}" "${OPENSHELL_PARITY_CANDIDATE_CARGO_TARGET_DIR:-}" "${OPENSHELL_PARITY_CANDIDATE_SUPERVISOR_BIN:-}" CANDIDATE_SUPERVISOR - -stage_artifact() { - local variant=$1 role=$2 source=$3 mode=$4 path_var=$5 digest_var=$6 - local destination="${RESULTS_DIR}/artifacts/${variant}/${role}" - mkdir -p "$(dirname "${destination}")" - install -m "${mode}" "${source}" "${destination}" - printf -v "${path_var}" '%s' "${destination}" - printf -v "${digest_var}" '%s' "$(sha256sum "${destination}" | cut -d' ' -f1)" -} - -stage_executable() { - stage_artifact "$1" "$2" "$3" 0555 "$4" "$5" -} - -BASELINE_GATEWAY_DIGEST="" BASELINE_CLI_DIGEST="" BASELINE_CONFORMANCE_DIGEST="" BASELINE_EXTERNAL_DRIVER_DIGEST="" BASELINE_SUPERVISOR_DIGEST="" BASELINE_SUPERVISOR_DOCKERFILE="" BASELINE_SUPERVISOR_DOCKERFILE_DIGEST="" BASELINE_CLI_TRACE_WRAPPER="" BASELINE_CLI_TRACE_WRAPPER_DIGEST="" -CANDIDATE_GATEWAY_DIGEST="" CANDIDATE_CLI_DIGEST="" CANDIDATE_CONFORMANCE_DIGEST="" CANDIDATE_EXTERNAL_DRIVER_DIGEST="" CANDIDATE_SUPERVISOR_DIGEST="" CANDIDATE_SUPERVISOR_DOCKERFILE="" CANDIDATE_SUPERVISOR_DOCKERFILE_DIGEST="" CANDIDATE_CLI_TRACE_WRAPPER="" CANDIDATE_CLI_TRACE_WRAPPER_DIGEST="" -stage_executable baseline gateway "${BASELINE_GATEWAY}" BASELINE_GATEWAY BASELINE_GATEWAY_DIGEST -stage_executable baseline cli "${BASELINE_CLI}" BASELINE_CLI BASELINE_CLI_DIGEST -stage_executable baseline conformance "${BASELINE_CONFORMANCE}" BASELINE_CONFORMANCE BASELINE_CONFORMANCE_DIGEST -stage_executable baseline supervisor "${BASELINE_SUPERVISOR}" BASELINE_SUPERVISOR BASELINE_SUPERVISOR_DIGEST -stage_artifact baseline supervisor.Dockerfile "${BASELINE_WORKTREE}/deploy/docker/Dockerfile.supervisor" 0444 BASELINE_SUPERVISOR_DOCKERFILE BASELINE_SUPERVISOR_DOCKERFILE_DIGEST -stage_artifact baseline cli-trace-wrapper "${ROOT}/e2e/parity/trace-cli.sh" 0555 BASELINE_CLI_TRACE_WRAPPER BASELINE_CLI_TRACE_WRAPPER_DIGEST -stage_executable candidate gateway "${CANDIDATE_GATEWAY}" CANDIDATE_GATEWAY CANDIDATE_GATEWAY_DIGEST -stage_executable candidate cli "${CANDIDATE_CLI}" CANDIDATE_CLI CANDIDATE_CLI_DIGEST -stage_executable candidate conformance "${CANDIDATE_CONFORMANCE}" CANDIDATE_CONFORMANCE CANDIDATE_CONFORMANCE_DIGEST -stage_executable candidate supervisor "${CANDIDATE_SUPERVISOR}" CANDIDATE_SUPERVISOR CANDIDATE_SUPERVISOR_DIGEST -stage_artifact candidate supervisor.Dockerfile "${CANDIDATE_WORKTREE}/deploy/docker/Dockerfile.supervisor" 0444 CANDIDATE_SUPERVISOR_DOCKERFILE CANDIDATE_SUPERVISOR_DOCKERFILE_DIGEST -stage_artifact candidate cli-trace-wrapper "${ROOT}/e2e/parity/trace-cli.sh" 0555 CANDIDATE_CLI_TRACE_WRAPPER CANDIDATE_CLI_TRACE_WRAPPER_DIGEST -if [ "${SCENARIO}" = external-driver ]; then - stage_executable baseline external-driver "${BASELINE_EXTERNAL_DRIVER}" BASELINE_EXTERNAL_DRIVER BASELINE_EXTERNAL_DRIVER_DIGEST - stage_executable candidate external-driver "${CANDIDATE_EXTERNAL_DRIVER}" CANDIDATE_EXTERNAL_DRIVER CANDIDATE_EXTERNAL_DRIVER_DIGEST - if [ "${BASELINE_EXTERNAL_DRIVER_DIGEST}" = "${CANDIDATE_EXTERNAL_DRIVER_DIGEST}" ]; then - echo "ERROR: external-driver parity requires different baseline and candidate driver content." >&2 - exit 2 - fi -fi - -require_executable "Podman parity wrapper" "${WRAPPER}" -if [ "${SCENARIO}" = "podman-options" ] && [ ! -f "${PODMAN_OPTIONS_ORACLE}" ]; then - echo "ERROR: Podman options oracle does not exist: ${PODMAN_OPTIONS_ORACLE}" >&2 - exit 2 -fi -if [ "${SCENARIO}" != "podman-options" ] && [ ! -f "${CONFORMANCE_TRACE_WRAPPER}" ]; then - echo "ERROR: conformance trace wrapper does not exist: ${CONFORMANCE_TRACE_WRAPPER}" >&2 - exit 2 -fi - -podman_in_resolver_store() { - local resolver_home="${RUN_DIR}/sandbox-resolver" - mkdir -p "${resolver_home}/config" "${resolver_home}/state" \ - "${resolver_home}/cache" "${resolver_home}/data" - env -u CONTAINER_HOST -u CONTAINER_CONNECTION -u CONTAINERS_STORAGE_CONF \ - -u CONTAINERS_CONF -u CONTAINERS_REGISTRIES_CONF -u CONTAINERS_REGISTRIES_CONF_DIR \ - -u CONTAINERS_POLICY -u PODMAN_CONNECTIONS_CONF -u DOCKER_HOST \ - XDG_CONFIG_HOME="${resolver_home}/config" \ - XDG_STATE_HOME="${resolver_home}/state" \ - XDG_CACHE_HOME="${resolver_home}/cache" \ - XDG_DATA_HOME="${resolver_home}/data" \ - "${PODMAN_BIN}" "$@" -} - -resolve_parity_sandbox_image() { - local image_id image_digest repository - if [ -n "${OPENSHELL_E2E_PODMAN_SANDBOX_IMAGE:-}" ] \ - && ! [[ "${OPENSHELL_E2E_PODMAN_SANDBOX_IMAGE}" =~ ^[^@]+@sha256:[0-9a-f]{64}$ ]]; then - echo "ERROR: OPENSHELL_E2E_PODMAN_SANDBOX_IMAGE must be digest-pinned for parity runs." >&2 - exit 2 - fi - case "${SANDBOX_IMAGE_REQUEST}" in - */*) ;; - *) - echo "ERROR: parity sandbox image must use a fully qualified repository: ${SANDBOX_IMAGE_REQUEST}" >&2 - exit 2 - ;; - esac - echo "Resolving parity sandbox image once: ${SANDBOX_IMAGE_REQUEST}" - podman_in_resolver_store pull "${SANDBOX_IMAGE_REQUEST}" >/dev/null - image_id="$(podman_in_resolver_store image inspect --format '{{.Id}}' "${SANDBOX_IMAGE_REQUEST}")" - image_id="${image_id#sha256:}" - image_digest="$(podman_in_resolver_store image inspect --format '{{.Digest}}' "${SANDBOX_IMAGE_REQUEST}")" - repository="${SANDBOX_IMAGE_REQUEST%%@*}" - case "${repository##*/}" in - *:*) repository="${repository%:*}" ;; - esac - PARITY_SANDBOX_RUNTIME_IMAGE="${repository}@${image_digest}" - if ! [[ "${image_id}" =~ ^[0-9a-f]{64}$ ]] \ - || ! [[ "${PARITY_SANDBOX_RUNTIME_IMAGE}" =~ ^[^@]+@sha256:[0-9a-f]{64}$ ]]; then - echo "ERROR: could not resolve one immutable parity sandbox image from ${SANDBOX_IMAGE_REQUEST}." >&2 - exit 2 - fi - echo "Using one immutable parity sandbox image for both variants: ${PARITY_SANDBOX_RUNTIME_IMAGE} (ID ${image_id})" -} - -resolve_parity_supervisor_base_image() { - local baseline_base candidate_base - baseline_base="$(awk '$1 == "FROM" { print $2; exit }' "${BASELINE_SUPERVISOR_DOCKERFILE}")" - candidate_base="$(awk '$1 == "FROM" { print $2; exit }' "${CANDIDATE_SUPERVISOR_DOCKERFILE}")" - if [ -z "${baseline_base}" ] || [ "${baseline_base}" != "${candidate_base}" ]; then - echo "ERROR: parity supervisor Dockerfiles must select the same base image." >&2 - exit 2 - fi - PARITY_SUPERVISOR_BASE_IMAGE="${baseline_base}" - echo "Resolving parity supervisor base image once: ${PARITY_SUPERVISOR_BASE_IMAGE}" - podman_in_resolver_store pull "${PARITY_SUPERVISOR_BASE_IMAGE}" >/dev/null - PARITY_SUPERVISOR_BASE_RUNTIME_IMAGE="$(podman_in_resolver_store image inspect --format '{{index .RepoDigests 0}}' "${PARITY_SUPERVISOR_BASE_IMAGE}")" - if ! [[ "${PARITY_SUPERVISOR_BASE_RUNTIME_IMAGE}" =~ ^[^@]+@sha256:[0-9a-f]{64}$ ]]; then - echo "ERROR: could not resolve one immutable supervisor base image from ${PARITY_SUPERVISOR_BASE_IMAGE}." >&2 - exit 2 - fi - echo "Using one immutable supervisor base image for both variants: ${PARITY_SUPERVISOR_BASE_RUNTIME_IMAGE}" -} - -resolve_parity_sandbox_image -resolve_parity_supervisor_base_image - -write_result() { - local variant=$1 source_sha=$2 schema=$3 status=$4 - local gateway_digest=$5 cli_digest=$6 conformance_digest=$7 external_driver_digest_value=$8 supervisor_digest=$9 supervisor_dockerfile_digest=${10} cli_trace_wrapper_digest=${11} - local normalized_result="" external_driver_digest="" gateway_profile="in-tree" - local gateway_features=default - local gateway_origin cli_origin conformance_origin external_driver_origin supervisor_origin - if [ "${variant}" = baseline ]; then - gateway_origin=${BASELINE_GATEWAY_ORIGIN} - cli_origin=${BASELINE_CLI_ORIGIN} - conformance_origin=${BASELINE_CONFORMANCE_ORIGIN} - external_driver_origin=${BASELINE_EXTERNAL_DRIVER_ORIGIN} - supervisor_origin=${BASELINE_SUPERVISOR_ORIGIN} - else - gateway_origin=${CANDIDATE_GATEWAY_ORIGIN} - cli_origin=${CANDIDATE_CLI_ORIGIN} - conformance_origin=${CANDIDATE_CONFORMANCE_ORIGIN} - external_driver_origin=${CANDIDATE_EXTERNAL_DRIVER_ORIGIN} - supervisor_origin=${CANDIDATE_SUPERVISOR_ORIGIN} - fi - if [ -n "${external_driver_digest_value}" ]; then - external_driver_digest=",\"external_driver_sha256\":\"${external_driver_digest_value}\"" - gateway_profile="driver-free" - gateway_features="--no-default-features --features telemetry" - fi - if [ "${SCENARIO}" = "podman-options" ]; then normalized_result=",\"normalized_result\":\"${variant}.normalized.json\""; fi - cat >"${RESULTS_DIR}/${variant}.json" <"${RUN_DIR}/baseline.semantic" - sed -E 's/"pids_limit":[0-9]+/"pids_limit":IGNORED/' "${RESULTS_DIR}/candidate.normalized.json" >"${RUN_DIR}/candidate.semantic" - if grep -F '"pids_limit":2048' "${RESULTS_DIR}/baseline.normalized.json" >/dev/null \ - && grep -F '"pids_limit":31' "${RESULTS_DIR}/candidate.normalized.json" >/dev/null \ - && cmp -s "${RUN_DIR}/baseline.semantic" "${RUN_DIR}/candidate.semantic"; then - COMPARISON_ACCEPTED=true - COMPARISON_CLASSIFICATION="intentional_change" - intentional_change_id='"podman-pid-limit-restored"' - fi - fi - fi - cat >"${RESULTS_DIR}/comparison.json" <&2 - return 1 - fi -} - -run_variant() { - local variant=$1 source_sha=$2 schema=$3 gateway=$4 cli=$5 conformance=$6 external_driver=$7 supervisor=$8 supervisor_dockerfile=$9 - local gateway_digest=${10} cli_digest=${11} conformance_digest=${12} external_driver_digest=${13} supervisor_digest=${14} supervisor_dockerfile_digest=${15} cli_trace_wrapper=${16} cli_trace_wrapper_digest=${17} - local result_status variant_home="${RUN_DIR}/${variant}" - local supervisor_image="localhost/openshell/supervisor:parity-${variant}-${source_sha:0:12}" - mkdir -p "${variant_home}/config" "${variant_home}/state" "${variant_home}/cache" "${variant_home}/data" - echo "==> schema parity ${variant} (schema v${schema}, ${DRIVER}, ${SCENARIO})" - local option_profile="" - local -a command - if [ "${SCENARIO}" = "podman-options" ]; then - option_profile="podman-options" - command=(bash "${PODMAN_OPTIONS_ORACLE}") - else - command=(bash "${CONFORMANCE_TRACE_WRAPPER}" run --openshell-bin "${cli_trace_wrapper}" --output json) - fi - verify_artifact_digest "${variant} gateway before execution" "${gateway}" "${gateway_digest}" || return 1 - verify_artifact_digest "${variant} CLI before execution" "${cli}" "${cli_digest}" || return 1 - verify_artifact_digest "${variant} conformance CLI before execution" "${conformance}" "${conformance_digest}" || return 1 - verify_artifact_digest "${variant} supervisor before execution" "${supervisor}" "${supervisor_digest}" || return 1 - verify_artifact_digest "${variant} supervisor Dockerfile before execution" "${supervisor_dockerfile}" "${supervisor_dockerfile_digest}" || return 1 - verify_artifact_digest "${variant} CLI trace wrapper before execution" "${cli_trace_wrapper}" "${cli_trace_wrapper_digest}" || return 1 - if [ -n "${external_driver}" ]; then - verify_artifact_digest "${variant} external driver before execution" "${external_driver}" "${external_driver_digest}" || return 1 - fi - if env -u OPENSHELL_GATEWAY_ENDPOINT -u OPENSHELL_GATEWAY_CONFIG \ - -u OPENSHELL_COMPUTE_DRIVER -u OPENSHELL_COMPUTE_DRIVER_SOCKET -u OPENSHELL_DRIVERS \ - -u OPENSHELL_PODMAN_SOCKET \ - -u CONTAINER_HOST -u CONTAINER_CONNECTION -u CONTAINERS_STORAGE_CONF \ - -u CONTAINERS_CONF -u CONTAINERS_REGISTRIES_CONF -u CONTAINERS_REGISTRIES_CONF_DIR \ - -u CONTAINERS_POLICY -u PODMAN_CONNECTIONS_CONF -u DOCKER_HOST \ - -u OPENSHELL_SANDBOX_IMAGE -u OPENSHELL_SANDBOX_RUNTIME_IMAGE \ - -u OPENSHELL_E2E_PODMAN_SANDBOX_IMAGE \ - -u OPENSHELL_GRPC_ENDPOINT -u OPENSHELL_PODMAN_HOST_GATEWAY_IP \ - -u OPENSHELL_PODMAN_USERNS -u OPENSHELL_PROVIDER_SPIFFE_WORKLOAD_API_SOCKET \ - -u OPENSHELL_E2E_PROVIDER_SPIFFE_SOCKET -u OPENSHELL_APP_ARMOR_PROFILE \ - -u OPENSHELL_SANDBOX_HTTPS_PROXY -u OPENSHELL_SANDBOX_NO_PROXY \ - -u OPENSHELL_SANDBOX_PROXY_AUTH_FILE -u OPENSHELL_SANDBOX_PROXY_AUTH_ALLOW_INSECURE \ - -u OPENSHELL_SANDBOX_PROXY_CONNECT_BY_HOSTNAME -u OPENSHELL_SANDBOX_PROXY_CA_BUNDLE \ - -u OPENSHELL_OTLP_ENDPOINT -u OPENSHELL_GATEWAY_NAME -u OPENSHELL_COMPUTE_DRIVER_BIND \ - OPENSHELL_PARITY_VARIANT="${variant}" \ - OPENSHELL_E2E_CONFIG_SCHEMA_VERSION="${schema}" \ - OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER="$([ "${SCENARIO}" = external-driver ] && printf 1 || printf 0)" \ - OPENSHELL_EXTERNAL_DRIVER_BIN="${external_driver}" \ - OPENSHELL_E2E_SUPERVISOR_BIN="${supervisor}" \ - OPENSHELL_E2E_SUPERVISOR_DOCKERFILE="${supervisor_dockerfile}" \ - OPENSHELL_E2E_FORCE_TEMP_PODMAN_SERVICE=1 \ - OPENSHELL_E2E_REQUIRE_DIGEST_PINNED_SANDBOX_IMAGE=1 \ - OPENSHELL_E2E_PODMAN_SANDBOX_IMAGE="${PARITY_SANDBOX_RUNTIME_IMAGE}" \ - OPENSHELL_E2E_SUPERVISOR_BASE_IMAGE="${PARITY_SUPERVISOR_BASE_IMAGE}" \ - OPENSHELL_E2E_SUPERVISOR_BASE_RUNTIME_IMAGE="${PARITY_SUPERVISOR_BASE_RUNTIME_IMAGE}" \ - OPENSHELL_E2E_EXPECTED_GATEWAY_SHA256="${gateway_digest}" \ - OPENSHELL_E2E_EXPECTED_CLI_SHA256="${cli_digest}" \ - OPENSHELL_E2E_EXPECTED_CONFORMANCE_SHA256="${conformance_digest}" \ - OPENSHELL_E2E_EXPECTED_EXTERNAL_DRIVER_SHA256="${external_driver_digest}" \ - OPENSHELL_E2E_EXPECTED_SUPERVISOR_SHA256="${supervisor_digest}" \ - OPENSHELL_E2E_EXPECTED_SUPERVISOR_DOCKERFILE_SHA256="${supervisor_dockerfile_digest}" \ - OPENSHELL_E2E_EXPECTED_CLI_TRACE_WRAPPER_SHA256="${cli_trace_wrapper_digest}" \ - OPENSHELL_PARITY_REAL_CLI="${cli}" \ - OPENSHELL_PARITY_REAL_CONFORMANCE="${conformance}" \ - OPENSHELL_PARITY_CONFORMANCE_REPORT_CAPTURE="${RESULTS_DIR}/${variant}.conformance.json" \ - OPENSHELL_PARITY_EXEC_STDOUT_CAPTURE="${RESULTS_DIR}/${variant}.exec.stdout" \ - OPENSHELL_PARITY_EXTERNAL_DRIVER_LOG_CAPTURE="${RESULTS_DIR}/${variant}.driver.log" \ - OPENSHELL_SUPERVISOR_IMAGE="${supervisor_image}" \ - OPENSHELL_E2E_PODMAN_OPTION_PROFILE="${option_profile}" \ - OPENSHELL_PARITY_ORACLE_RESULT="${RESULTS_DIR}/${variant}.normalized.json" \ - OPENSHELL_PARITY_GATEWAY_CONFIG_CAPTURE="${RESULTS_DIR}/${variant}.gateway.toml" \ - OPENSHELL_PARITY_LAUNCH_MANIFEST_CAPTURE="${RESULTS_DIR}/${variant}.launch.json" \ - OPENSHELL_PARITY_SUPERVISOR_PACKAGE_CAPTURE="${RESULTS_DIR}/artifacts/${variant}/supervisor.packages.txt" \ - OPENSHELL_GATEWAY_BIN="${gateway}" \ - OPENSHELL_BIN="${cli}" \ - OPENSHELL_CONFORMANCE_BIN="${conformance}" \ - MISE_TRUSTED_CONFIG_PATHS="${MISE_TRUSTED_CONFIG_PATHS:-${ROOT}}" \ - XDG_CONFIG_HOME="${variant_home}/config" \ - XDG_STATE_HOME="${variant_home}/state" \ - XDG_CACHE_HOME="${variant_home}/cache" \ - XDG_DATA_HOME="${variant_home}/data" \ - "${WRAPPER}" "${command[@]}" \ - 2>&1 | tee "${RESULTS_DIR}/${variant}.log"; then - result_status=true - else - result_status=false - fi - if [ ! -s "${RESULTS_DIR}/${variant}.launch.json" ]; then - echo "ERROR: ${variant} launcher did not emit a launch manifest." >&2 - result_status=false - fi - verify_artifact_digest "${variant} gateway" "${gateway}" "${gateway_digest}" || result_status=false - verify_artifact_digest "${variant} CLI" "${cli}" "${cli_digest}" || result_status=false - verify_artifact_digest "${variant} conformance CLI" "${conformance}" "${conformance_digest}" || result_status=false - verify_artifact_digest "${variant} supervisor" "${supervisor}" "${supervisor_digest}" || result_status=false - verify_artifact_digest "${variant} supervisor Dockerfile" "${supervisor_dockerfile}" "${supervisor_dockerfile_digest}" || result_status=false - verify_artifact_digest "${variant} CLI trace wrapper" "${cli_trace_wrapper}" "${cli_trace_wrapper_digest}" || result_status=false - if [ -n "${external_driver}" ]; then - verify_artifact_digest "${variant} external driver" "${external_driver}" "${external_driver_digest}" || result_status=false - fi - if [ "${SCENARIO}" != podman-options ] \ - && [ ! -s "${RESULTS_DIR}/${variant}.exec.stdout" ]; then - echo "ERROR: ${variant} CLI trace wrapper did not retain exec stdout." >&2 - result_status=false - fi - if [ "${SCENARIO}" != podman-options ] \ - && [ ! -s "${RESULTS_DIR}/${variant}.conformance.json" ]; then - echo "ERROR: ${variant} conformance trace wrapper did not retain a report." >&2 - result_status=false - fi - if [ "${SCENARIO}" = external-driver ] && [ ! -s "${RESULTS_DIR}/${variant}.driver.log" ]; then - echo "ERROR: ${variant} external driver log was not retained." >&2 - result_status=false - fi - write_result "${variant}" "${source_sha}" "${schema}" "${result_status}" "${gateway_digest}" "${cli_digest}" "${conformance_digest}" "${external_driver_digest}" "${supervisor_digest}" "${supervisor_dockerfile_digest}" "${cli_trace_wrapper_digest}" - [ "${result_status}" = true ] -} - -baseline_exit=0 -candidate_exit=0 -run_variant baseline "${BASELINE_SHA}" 1 "${BASELINE_GATEWAY}" "${BASELINE_CLI}" "${BASELINE_CONFORMANCE}" "${BASELINE_EXTERNAL_DRIVER}" "${BASELINE_SUPERVISOR}" "${BASELINE_SUPERVISOR_DOCKERFILE}" "${BASELINE_GATEWAY_DIGEST}" "${BASELINE_CLI_DIGEST}" "${BASELINE_CONFORMANCE_DIGEST}" "${BASELINE_EXTERNAL_DRIVER_DIGEST}" "${BASELINE_SUPERVISOR_DIGEST}" "${BASELINE_SUPERVISOR_DOCKERFILE_DIGEST}" "${BASELINE_CLI_TRACE_WRAPPER}" "${BASELINE_CLI_TRACE_WRAPPER_DIGEST}" || baseline_exit=$? -# Do not short-circuit: a candidate result is useful even when the frozen -# baseline failed, and two equal failures must never constitute parity. -run_variant candidate "${CANDIDATE_SHA}" 2 "${CANDIDATE_GATEWAY}" "${CANDIDATE_CLI}" "${CANDIDATE_CONFORMANCE}" "${CANDIDATE_EXTERNAL_DRIVER}" "${CANDIDATE_SUPERVISOR}" "${CANDIDATE_SUPERVISOR_DOCKERFILE}" "${CANDIDATE_GATEWAY_DIGEST}" "${CANDIDATE_CLI_DIGEST}" "${CANDIDATE_CONFORMANCE_DIGEST}" "${CANDIDATE_EXTERNAL_DRIVER_DIGEST}" "${CANDIDATE_SUPERVISOR_DIGEST}" "${CANDIDATE_SUPERVISOR_DOCKERFILE_DIGEST}" "${CANDIDATE_CLI_TRACE_WRAPPER}" "${CANDIDATE_CLI_TRACE_WRAPPER_DIGEST}" || candidate_exit=$? - -baseline_success=$([ "${baseline_exit}" -eq 0 ] && printf true || printf false) -candidate_success=$([ "${candidate_exit}" -eq 0 ] && printf true || printf false) -semantic_verified=false -if [ "${baseline_success}" = true ] && [ "${candidate_success}" = true ] \ - && [ "${SCENARIO}" != podman-options ]; then - if [ ! -f "${RESULTS_VERIFIER}" ]; then - echo "ERROR: parity semantic verifier not found: ${RESULTS_VERIFIER}" >&2 - elif python3 "${RESULTS_VERIFIER}" \ - --baseline-sha "${BASELINE_SHA}" \ - --candidate-sha "${CANDIDATE_SHA}" \ - --scenario "${SCENARIO}" \ - --results-dir "${RESULTS_DIR}" \ - --output "${RESULTS_DIR}/semantic-verification.json" \ - && [ -s "${RESULTS_DIR}/semantic-verification.json" ]; then - semantic_verified=true - else - echo "ERROR: semantic verification failed for ${SCENARIO}." >&2 - fi -fi -write_comparison "${baseline_success}" "${candidate_success}" "${semantic_verified}" - -if [ "${COMPARISON_ACCEPTED}" != true ]; then - echo "ERROR: schema parity comparison classified ${SCENARIO} as a regression." >&2 - exit 1 -fi -if [ "${COMPARISON_CLASSIFICATION}" = intentional_change ]; then - echo "Schema parity accepted an intentional change: podman-pid-limit-restored (${SCENARIO})." -else - echo "Schema parity passed: baseline schema v1 and candidate schema v2 succeeded (${SCENARIO})." -fi diff --git a/e2e/parity/test.sh b/e2e/parity/test.sh deleted file mode 100755 index 4de6824f05..0000000000 --- a/e2e/parity/test.sh +++ /dev/null @@ -1,593 +0,0 @@ -#!/usr/bin/env bash -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Deterministic contract tests for e2e/parity/run.sh. No container runtime is -# invoked; the Podman wrapper and all three artifacts are tiny local fakes. - -set -euo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -TEST_SUPERVISOR_BASE="$(awk '$1 == "FROM" { print $2; exit }' "${ROOT}/deploy/docker/Dockerfile.supervisor")" -TMP_ROOT="${TMPDIR:-/tmp}" -TMP_ROOT="${TMP_ROOT%/}" -WORKDIR="$(mktemp -d "${TMP_ROOT}/openshell-parity-test.XXXXXX")" -trap 'rm -rf "${WORKDIR}"' EXIT - -fail() { echo "FAIL: $*" >&2; exit 1; } -assert_contains() { grep -F -- "$2" "$1" >/dev/null || fail "expected $1 to contain: $2"; } -assert_not_contains() { ! grep -F -- "$2" "$1" >/dev/null || fail "expected $1 not to contain: $2"; } -assert_status() { [ "$1" -eq "$2" ] || fail "expected status $2, got $1"; } - -# Package provenance must work for both Debian status and distroless status.d. -uv run --no-project python - "${ROOT}" "${WORKDIR}" <<'PYTEST' -import runpy -import sys -from pathlib import Path - -manifest = runpy.run_path(str(Path(sys.argv[1]) / "e2e/support/debian-package-manifest.py"))["package_manifest"] -root = Path(sys.argv[2]) / "dpkg-fixture" -root.mkdir() -(root / "status").write_text("Package: removed\nStatus: deinstall ok config-files\n\n") -(root / "status.d").mkdir() -(root / "status.d/libc6").write_text("Package: libc6\nVersion: 2.41\nArchitecture: arm64\nMulti-Arch: same\n") -(root / "status.d/libc6.md5sums").write_text("ignored checksum file") -assert manifest(root) == ["libc6:arm64=2.41"] -(root / "status").write_text("Package: ca-certificates\nStatus: install ok installed\nVersion: 20250419\nArchitecture: all\n") -assert manifest(root) == ["ca-certificates=20250419", "libc6:arm64=2.41"] -(root / "status.d/libc6").write_text("Package: broken\n") -try: - manifest(root) -except ValueError: - pass -else: - raise AssertionError("incomplete package metadata accepted") -try: - manifest(root / "missing") -except ValueError: - pass -else: - raise AssertionError("missing package metadata accepted") -PYTEST - -# Schema generator behavior is separately deterministic and does not need a -# gateway, certificates, or Podman. -# shellcheck source=e2e/support/gateway-common.sh -source "${ROOT}/e2e/support/gateway-common.sh" -# shellcheck source=e2e/support/podman-gateway-config.sh -source "${ROOT}/e2e/support/podman-gateway-config.sh" -mkdir -p "${WORKDIR}/pki/client" "${WORKDIR}/jwt" -e2e_write_podman_gateway_config "${WORKDIR}/v1.toml" 1 "${ROOT}" "${WORKDIR}/pki" "${WORKDIR}/jwt" test-gateway 0 socket network 18181 image:test 15 supervisor:test sandbox:test '' '' 0 '' -e2e_write_podman_gateway_config "${WORKDIR}/v2.toml" 2 "${ROOT}" "${WORKDIR}/pki" "${WORKDIR}/jwt" test-gateway 0 socket network 18181 image:test 15 supervisor:test sandbox:test '' '' 0 '' -e2e_write_podman_gateway_config "${WORKDIR}/v2-external.toml" 2 "${ROOT}" "${WORKDIR}/pki" "${WORKDIR}/jwt" test-gateway 1 socket network 18181 image:test 15 supervisor:test sandbox:test '' '' 0 '' -assert_contains "${WORKDIR}/v1.toml" 'version = 1' -assert_contains "${WORKDIR}/v1.toml" 'compute_drivers = ["podman"]' -assert_contains "${WORKDIR}/v1.toml" 'image_pull_policy = "missing"' -assert_contains "${WORKDIR}/v1.toml" 'health_check_interval_secs = 0' -assert_contains "${WORKDIR}/v1.toml" 'sandbox_runtime_image = "sandbox:test"' -assert_contains "${WORKDIR}/v1.toml" 'guest_tls_ca = ' -assert_contains "${WORKDIR}/v2.toml" 'version = 2' -assert_contains "${WORKDIR}/v2.toml" 'compute_driver = "podman"' -assert_contains "${WORKDIR}/v2.toml" 'image_pull_policy = "if_not_present"' -assert_contains "${WORKDIR}/v2.toml" 'allow_driver_config = true' -assert_contains "${WORKDIR}/v2.toml" 'sandbox_runtime_image = "sandbox:test"' -assert_contains "${WORKDIR}/v2.toml" '[openshell.drivers.podman.resource_admission]' -assert_not_contains "${WORKDIR}/v2.toml" 'health_check_interval_secs = 0' -assert_contains "${WORKDIR}/v2-external.toml" 'socket_path = "socket"' -assert_not_contains "${WORKDIR}/v2-external.toml" 'sandbox_runtime_image = "sandbox:test"' -assert_not_contains "${WORKDIR}/v2-external.toml" 'allow_driver_config = true' -assert_not_contains "${WORKDIR}/v2-external.toml" '[openshell.drivers.podman.resource_admission]' -# V2 guest TLS is emitted before its driver table; V1 is driver-local. -OPENSHELL_E2E_PODMAN_OPTION_PROFILE=podman-options e2e_write_podman_gateway_config "${WORKDIR}/v1-options.toml" 1 "${ROOT}" "${WORKDIR}/pki" "${WORKDIR}/jwt" test-gateway 0 socket network 18181 image:test 15 supervisor:test sandbox:test "" "" 0 "" -OPENSHELL_E2E_PODMAN_OPTION_PROFILE=podman-options e2e_write_podman_gateway_config "${WORKDIR}/v2-options.toml" 2 "${ROOT}" "${WORKDIR}/pki" "${WORKDIR}/jwt" test-gateway 0 socket network 18181 image:test 15 supervisor:test sandbox:test "" "" 0 "" -for config in "${WORKDIR}/v1-options.toml" "${WORKDIR}/v2-options.toml"; do - assert_contains "${config}" 'sandbox_pids_limit = 31' - assert_contains "${config}" 'health_check_interval_secs = 7' - assert_not_contains "${config}" 'app_armor_profile = ' -done -assert_contains "${WORKDIR}/v1-options.toml" 'sandbox_ssh_socket_path = "/run/openshell/parity-ssh.sock"' -assert_contains "${WORKDIR}/v2-options.toml" 'ssh_socket_path = "/run/openshell/parity-ssh.sock"' -if OPENSHELL_E2E_PODMAN_OPTION_PROFILE=unknown e2e_podman_option_profile >/dev/null 2>&1; then fail 'unknown option profile unexpectedly accepted'; fi - -v1_driver_line="$(grep -n '^\[openshell.drivers.podman\]' "${WORKDIR}/v1.toml" | cut -d: -f1)" -v1_tls_line="$(grep -n '^guest_tls_ca' "${WORKDIR}/v1.toml" | cut -d: -f1)" -v2_driver_line="$(grep -n '^\[openshell.drivers.podman\]' "${WORKDIR}/v2.toml" | cut -d: -f1)" -v2_tls_line="$(grep -n '^guest_tls_ca' "${WORKDIR}/v2.toml" | cut -d: -f1)" -[ "${v1_tls_line}" -gt "${v1_driver_line}" ] || fail 'v1 TLS must be driver-local' -[ "${v2_tls_line}" -lt "${v2_driver_line}" ] || fail 'v2 TLS must be gateway-owned' -if OPENSHELL_E2E_CONFIG_SCHEMA_VERSION=3 e2e_podman_config_schema_version >/dev/null 2>&1; then - fail 'invalid schema version unexpectedly accepted' -fi -set +e -env -u OPENSHELL_GATEWAY_ENDPOINT \ - OPENSHELL_E2E_CONFIG_SCHEMA_VERSION=3 \ - bash "${ROOT}/e2e/with-podman-gateway.sh" true >"${WORKDIR}/wrapper-schema.out" 2>&1 -status=$? -set -e -assert_status "${status}" 2 -assert_contains "${WORKDIR}/wrapper-schema.out" 'must be 1 or 2' - -cat >"${WORKDIR}/trace-cli-fixture" <<'EOF' -#!/usr/bin/env bash -printf 'openshell-conformance-tracefixture\n' -printf 'trace fixture stderr\n' >&2 -EOF -chmod +x "${WORKDIR}/trace-cli-fixture" -OPENSHELL_PARITY_REAL_CLI="${WORKDIR}/trace-cli-fixture" \ -OPENSHELL_PARITY_EXEC_STDOUT_CAPTURE="${WORKDIR}/trace-cli.stdout" \ - bash "${ROOT}/e2e/parity/trace-cli.sh" sandbox exec -- echo marker \ - >"${WORKDIR}/trace-cli.forwarded.stdout" \ - 2>"${WORKDIR}/trace-cli.forwarded.stderr" -cmp -s "${WORKDIR}/trace-cli.stdout" "${WORKDIR}/trace-cli.forwarded.stdout" \ - || fail 'CLI trace wrapper did not preserve exact exec stdout' -assert_contains "${WORKDIR}/trace-cli.forwarded.stderr" 'trace fixture stderr' - -HEAD_SHA="$(git -C "${ROOT}" rev-parse HEAD)" -cat >"${WORKDIR}/manifest.toml" <"${WORKDIR}/bin/fake-wrapper" <<'EOF' -#!/usr/bin/env bash -set -euo pipefail -for variable in \ - OPENSHELL_GATEWAY_ENDPOINT OPENSHELL_GATEWAY_CONFIG OPENSHELL_COMPUTE_DRIVER \ - OPENSHELL_COMPUTE_DRIVER_SOCKET OPENSHELL_DRIVERS OPENSHELL_PODMAN_SOCKET \ - CONTAINER_HOST CONTAINER_CONNECTION CONTAINERS_STORAGE_CONF CONTAINERS_CONF \ - CONTAINERS_REGISTRIES_CONF CONTAINERS_REGISTRIES_CONF_DIR CONTAINERS_POLICY \ - PODMAN_CONNECTIONS_CONF DOCKER_HOST OPENSHELL_SANDBOX_IMAGE \ - OPENSHELL_SANDBOX_RUNTIME_IMAGE \ - OPENSHELL_GRPC_ENDPOINT OPENSHELL_PODMAN_HOST_GATEWAY_IP OPENSHELL_PODMAN_USERNS \ - OPENSHELL_PROVIDER_SPIFFE_WORKLOAD_API_SOCKET OPENSHELL_E2E_PROVIDER_SPIFFE_SOCKET \ - OPENSHELL_APP_ARMOR_PROFILE OPENSHELL_SANDBOX_HTTPS_PROXY OPENSHELL_SANDBOX_NO_PROXY \ - OPENSHELL_SANDBOX_PROXY_AUTH_FILE OPENSHELL_SANDBOX_PROXY_AUTH_ALLOW_INSECURE \ - OPENSHELL_SANDBOX_PROXY_CONNECT_BY_HOSTNAME OPENSHELL_SANDBOX_PROXY_CA_BUNDLE \ - OPENSHELL_OTLP_ENDPOINT OPENSHELL_GATEWAY_NAME OPENSHELL_COMPUTE_DRIVER_BIND; do - [ -z "${!variable:-}" ] || exit 23 -done -expected_sandbox="nvcr.io/nvidia/base/ubuntu@sha256:$(printf '%064d' 0)" -[ "${OPENSHELL_E2E_REQUIRE_DIGEST_PINNED_SANDBOX_IMAGE:-0}" = 1 ] || exit 24 -[ "${OPENSHELL_E2E_PODMAN_SANDBOX_IMAGE:-}" = "${expected_sandbox}" ] || exit 25 -expected_base="docker.io/library/debian@sha256:$(printf '%064d' 0)" -[ "${OPENSHELL_E2E_SUPERVISOR_BASE_IMAGE:-}" = "${OPENSHELL_PARITY_TEST_SUPERVISOR_BASE}" ] || exit 26 -[ "${OPENSHELL_E2E_SUPERVISOR_BASE_RUNTIME_IMAGE:-}" = "${expected_base}" ] || exit 27 -printf '%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s\n' "$OPENSHELL_PARITY_VARIANT" "$OPENSHELL_E2E_CONFIG_SCHEMA_VERSION" "$OPENSHELL_GATEWAY_BIN" "$OPENSHELL_BIN" "$OPENSHELL_CONFORMANCE_BIN" "$MISE_TRUSTED_CONFIG_PATHS" "${OPENSHELL_E2E_PODMAN_OPTION_PROFILE:-}" "${OPENSHELL_PARITY_ORACLE_RESULT:-}" "${OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER:-}" "${OPENSHELL_EXTERNAL_DRIVER_BIN:-}" "${OPENSHELL_E2E_SUPERVISOR_BIN:-}" >>"$OPENSHELL_PARITY_TEST_CALLS" -mkdir -p "$XDG_DATA_HOME/containers/storage" -printf 'fixture-package-1.0-r0\n' >"${OPENSHELL_PARITY_SUPERVISOR_PACKAGE_CAPTURE}" -package_hash="$(sha256sum "${OPENSHELL_PARITY_SUPERVISOR_PACKAGE_CAPTURE}" | cut -d' ' -f1)" -OPENSHELL_PARITY_FIXTURE_PACKAGE_HASH="${package_hash}" python3 - <<'PY' -import json -import os -from pathlib import Path - -variant = os.environ["OPENSHELL_PARITY_VARIANT"] -schema = int(os.environ["OPENSHELL_E2E_CONFIG_SCHEMA_VERSION"]) -external = os.environ.get("OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER") == "1" -zero = "0" * 64 -image_digest = f"sha256:{zero}" -sandbox_runtime = f"nvcr.io/nvidia/base/ubuntu@{image_digest}" -sandbox_boundary = "localhost/openshell/sandbox:dev" -supervisor_runtime = f"localhost/openshell/supervisor@{image_digest}" -base_runtime = f"docker.io/library/debian@{image_digest}" -pull_policy = "missing" if schema == 1 else "if_not_present" -gateway_port = 18181 -grpc_endpoint = f"https://127.0.0.1:{gateway_port}" -driver_socket = f"/tmp/{variant}-driver.sock" -podman_socket = f"/tmp/{variant}-podman.sock" -network = f"{variant}-network" - -selector = ( - 'compute_drivers = ["podman"]' - if schema == 1 - else 'compute_driver = "podman"' -) -config_lines = [ - "[openshell]", - f"version = {schema}", - "[openshell.gateway]", - selector, - "[openshell.drivers.podman]", - f'socket_path = "{driver_socket}"', -] -if not external: - config_lines.extend( - [ - f'network_name = "{network}"', - f'default_image = "{sandbox_runtime}"', - f'image_pull_policy = "{pull_policy}"', - f'supervisor_image = "{supervisor_runtime}"', - ] - ) -Path(os.environ["OPENSHELL_PARITY_GATEWAY_CONFIG_CAPTURE"]).write_text( - "\n".join(config_lines) + "\n", encoding="utf-8" -) - -launch = { - "schema_version": schema, - "gateway_port": gateway_port, - "external_compute_driver": external, - "compute_driver_transport": "remote_uds" if external else "in_tree", - "external_driver_pull_policy": pull_policy, - "supervisor_image": os.environ["OPENSHELL_SUPERVISOR_IMAGE"], - "supervisor_image_id": zero, - "supervisor_image_digest": image_digest, - "supervisor_runtime_image": supervisor_runtime, - "supervisor_base_image": os.environ["OPENSHELL_PARITY_TEST_SUPERVISOR_BASE"], - "supervisor_base_image_id": zero, - "supervisor_base_image_digest": image_digest, - "supervisor_base_runtime_image": base_runtime, - "supervisor_package_manifest_sha256": os.environ[ - "OPENSHELL_PARITY_FIXTURE_PACKAGE_HASH" - ], - "sandbox_image_request": sandbox_runtime, - "sandbox_image_id": zero, - "sandbox_image_digest": image_digest, - "sandbox_runtime_image": sandbox_runtime, - "sandbox_boundary_image": sandbox_boundary, - "sandbox_client_image_alias": "nvcr.io/nvidia/base/ubuntu:24.04", - "sandbox_client_image_alias_id": zero, - "gateway_sha256_before_execution": os.environ[ - "OPENSHELL_E2E_EXPECTED_GATEWAY_SHA256" - ], - "cli_sha256_before_execution": os.environ["OPENSHELL_E2E_EXPECTED_CLI_SHA256"], - "conformance_sha256_before_execution": os.environ[ - "OPENSHELL_E2E_EXPECTED_CONFORMANCE_SHA256" - ], - "external_driver_sha256_before_execution": os.environ.get( - "OPENSHELL_E2E_EXPECTED_EXTERNAL_DRIVER_SHA256", "" - ), - "supervisor_sha256_before_execution": os.environ[ - "OPENSHELL_E2E_EXPECTED_SUPERVISOR_SHA256" - ], - "supervisor_dockerfile_sha256_before_execution": os.environ[ - "OPENSHELL_E2E_EXPECTED_SUPERVISOR_DOCKERFILE_SHA256" - ], - "cli_trace_wrapper_sha256_before_execution": os.environ[ - "OPENSHELL_E2E_EXPECTED_CLI_TRACE_WRAPPER_SHA256" - ], -} -if external: - launch.update( - { - "external_driver_grpc_endpoint": grpc_endpoint, - "external_driver_host_gateway_ip": "host-gateway", - "external_driver_userns": None, - "external_driver_spiffe": False, - "external_driver_proxy": False, - "external_driver_app_armor": False, - "external_driver_environment": { - "XDG_DATA_HOME": f"/tmp/{variant}-driver-data", - "OPENSHELL_COMPUTE_DRIVER_SOCKET": driver_socket, - "OPENSHELL_PODMAN_SOCKET": podman_socket, - "OPENSHELL_SANDBOX_IMAGE": sandbox_runtime, - "OPENSHELL_SANDBOX_IMAGE_PULL_POLICY": pull_policy, - "OPENSHELL_SANDBOX_RUNTIME_IMAGE": sandbox_boundary, - "OPENSHELL_HEALTH_CHECK_INTERVAL_SECS": 10, - "OPENSHELL_GRPC_ENDPOINT": grpc_endpoint, - "OPENSHELL_GATEWAY_PORT": gateway_port, - "OPENSHELL_NETWORK_NAME": network, - "OPENSHELL_STOP_TIMEOUT": 15, - "OPENSHELL_SUPERVISOR_IMAGE": supervisor_runtime, - "OPENSHELL_PODMAN_TLS_CA": { - "path": f"/tmp/{variant}-pki/ca.crt", - "sha256": "8" * 64, - }, - "OPENSHELL_PODMAN_TLS_CERT": { - "path": f"/tmp/{variant}-pki/tls.crt", - "sha256": "9" * 64, - }, - "OPENSHELL_PODMAN_TLS_KEY": { - "path": f"/tmp/{variant}-pki/tls.key", - "sha256": "a" * 64, - }, - "OPENSHELL_ENABLE_BIND_MOUNTS": True, - }, - } - ) -if external and schema == 2: - del launch["external_driver_environment"]["OPENSHELL_PODMAN_TLS_CERT"] - del launch["external_driver_environment"]["OPENSHELL_PODMAN_TLS_KEY"] -Path(os.environ["OPENSHELL_PARITY_LAUNCH_MANIFEST_CAPTURE"]).write_text( - json.dumps(launch, separators=(",", ":")) + "\n", encoding="utf-8" -) -PY -run_id="fixture${OPENSHELL_PARITY_VARIANT}" -printf 'openshell-conformance-%s\n' "${run_id}" >"${OPENSHELL_PARITY_EXEC_STDOUT_CAPTURE}" -printf 'CLI conformance run ID: %s\n' "${run_id}" >&2 -printf 'gateway preflight connected: gateway=fixture, authentication=authenticated\n' >&2 -for marker in status create get-ready list-visible/0 exec delete list-empty/query/0; do - printf '[run %s][smoke/%s] completed in 1ms: exit 0\n' "${run_id}" "${marker}" >&2 -done -printf '%s %064d sha256:%064d %s %s %s %s\n' \ - "${expected_sandbox}" 0 0 "${expected_base}" \ - "localhost/openshell/supervisor@sha256:$(printf '%064d' 0)" \ - "nvcr.io/nvidia/base/ubuntu:24.04" \ - "${package_hash}" >&2 -if [ "${OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER:-0}" = 1 ]; then - printf 'fixture external driver log\n' >"${OPENSHELL_PARITY_EXTERNAL_DRIVER_LOG_CAPTURE}" -fi -if [ "${OPENSHELL_PARITY_TEST_MUTATE_ARTIFACT:-}" = "${OPENSHELL_PARITY_VARIANT}" ]; then - replacement="${OPENSHELL_GATEWAY_BIN}.replacement" - printf '#!/usr/bin/env bash\nexit 0\n# mutated\n' >"${replacement}" - chmod 0555 "${replacement}" - mv -f "${replacement}" "${OPENSHELL_GATEWAY_BIN}" -fi -if [ "${OPENSHELL_E2E_PODMAN_OPTION_PROFILE:-}" = podman-options ]; then - case "${OPENSHELL_PARITY_VARIANT}" in baseline) pids=2048 ;; candidate) pids=31 ;; esac - stable=true - if [ "${OPENSHELL_PARITY_TEST_SEMANTIC_DRIFT:-0}" = 1 ] && [ "${OPENSHELL_PARITY_VARIANT}" = candidate ]; then stable=false; fi - if [ "${OPENSHELL_PARITY_TEST_SKIP_RESULT:-}" != "${OPENSHELL_PARITY_VARIANT}" ]; then - printf '%s\n' "{\"scenario\":\"podman-options\",\"stable\":${stable},\"pids_limit\":${pids}}" > "${OPENSHELL_PARITY_ORACLE_RESULT}" - fi -fi -exec "$@" -EOF -cat >"${WORKDIR}/bin/fake-podman" <<'EOF' -#!/usr/bin/env bash -set -euo pipefail -printf '%s\n' "$*" >>"$OPENSHELL_PARITY_TEST_PODMAN_CALLS" -case "$1" in - pull) exit 0 ;; - image) - [ "$2" = inspect ] || exit 19 - case "$4" in - '{{.Id}}') printf 'sha256:%064d\n' 0 ;; - '{{.Digest}}') printf 'sha256:%064d\n' 0 ;; - '{{index .RepoDigests 0}}') printf 'docker.io/library/debian@sha256:%064d\n' 0 ;; - *) exit 19 ;; - esac - ;; - unshare) - shift - exec "$@" - ;; - *) exit 19 ;; -esac -EOF -cat >"${WORKDIR}/bin/fake-conformance" <<'EOF' -#!/usr/bin/env bash -set -euo pipefail -if [ "${OPENSHELL_PARITY_FAIL_VARIANT:-}" = "${OPENSHELL_PARITY_VARIANT:-}" ] || [ "${OPENSHELL_PARITY_FAIL_VARIANT:-}" = both ]; then - exit 17 -fi -if [ "${OPENSHELL_PARITY_TEST_INVALID_REPORT:-0}" = 1 ]; then - # Prove the verifier parses retained stdout instead of accepting a success - # substring injected into unrelated raw stderr. - printf '%s\n' '"passed": true' >&2 - printf '%s\n' '{"scenarios":[{"name":"smoke","passed":false,"diagnostic":"fixture failure"}],"passed":false}' -else - printf '%s\n' '{' - printf '%s\n' ' "scenarios": [{"name":"smoke","passed":true,"diagnostic":null}],' - printf '%s\n' ' "passed": true' - printf '%s\n' '}' -fi -EOF -for artifact in baseline-gateway baseline-cli candidate-gateway candidate-cli baseline-driver candidate-driver baseline-supervisor candidate-supervisor; do - printf '#!/usr/bin/env bash\n# %s\nexit 0\n' "${artifact}" >"${WORKDIR}/bin/${artifact}" -done -chmod +x "${WORKDIR}/bin/"* - -# shellcheck source=e2e/support/podman-gateway-config.sh -source "${ROOT}/e2e/support/podman-gateway-config.sh" -[ "$(e2e_podman_external_driver_pull_policy 1)" = missing ] || fail 'schema v1 external pull policy mismatch' -[ "$(e2e_podman_external_driver_pull_policy 2)" = if_not_present ] || fail 'schema v2 external pull policy mismatch' - -run_harness() { - if [ "${OPENSHELL_PARITY_TEST_KEEP_RESULTS_DIR:-0}" != 1 ]; then - rm -rf -- "${WORKDIR}/results" - fi - OPENSHELL_PARITY_TEST_SUPERVISOR_BASE="${TEST_SUPERVISOR_BASE}" \ - OPENSHELL_PARITY_CAPABILITY_MANIFEST="${WORKDIR}/manifest.toml" \ - OPENSHELL_PARITY_BASELINE_WORKTREE="${ROOT}" \ - OPENSHELL_PARITY_PODMAN_WRAPPER="${WORKDIR}/bin/fake-wrapper" \ - OPENSHELL_PARITY_PODMAN_BIN="${WORKDIR}/bin/fake-podman" \ - OPENSHELL_PARITY_PODMAN_OPTIONS_ORACLE="${WORKDIR}/bin/fake-conformance" \ - OPENSHELL_PARITY_BASELINE_GATEWAY_BIN="${WORKDIR}/bin/baseline-gateway" \ - OPENSHELL_PARITY_BASELINE_CLI_BIN="${WORKDIR}/bin/baseline-cli" \ - OPENSHELL_PARITY_BASELINE_CONFORMANCE_BIN="${WORKDIR}/bin/fake-conformance" \ - OPENSHELL_PARITY_CANDIDATE_GATEWAY_BIN="${WORKDIR}/bin/candidate-gateway" \ - OPENSHELL_PARITY_CANDIDATE_CLI_BIN="${WORKDIR}/bin/candidate-cli" \ - OPENSHELL_PARITY_CANDIDATE_CONFORMANCE_BIN="${WORKDIR}/bin/fake-conformance" \ - OPENSHELL_PARITY_BASELINE_EXTERNAL_DRIVER_BIN="${WORKDIR}/bin/baseline-driver" \ - OPENSHELL_PARITY_CANDIDATE_EXTERNAL_DRIVER_BIN="${OPENSHELL_PARITY_TEST_CANDIDATE_DRIVER_OVERRIDE:-${WORKDIR}/bin/candidate-driver}" \ - OPENSHELL_PARITY_BASELINE_SUPERVISOR_BIN="${WORKDIR}/bin/baseline-supervisor" \ - OPENSHELL_PARITY_CANDIDATE_SUPERVISOR_BIN="${WORKDIR}/bin/candidate-supervisor" \ - OPENSHELL_PARITY_RESULTS_DIR="${WORKDIR}/results" \ - OPENSHELL_PARITY_TEST_CALLS="${WORKDIR}/calls" \ - OPENSHELL_PARITY_TEST_PODMAN_CALLS="${WORKDIR}/podman-calls" \ - MISE_TRUSTED_CONFIG_PATHS= \ - bash "${ROOT}/e2e/parity/run.sh" --driver podman "$@" -} - -OPENSHELL_GATEWAY_ENDPOINT=http://127.0.0.1:9 \ -OPENSHELL_GATEWAY_CONFIG=/tmp/untrusted.toml \ -OPENSHELL_COMPUTE_DRIVER=wrong \ -OPENSHELL_COMPUTE_DRIVER_SOCKET=/tmp/untrusted.sock \ -OPENSHELL_DRIVERS=wrong \ -OPENSHELL_PODMAN_SOCKET=/tmp/untrusted-podman.sock \ -CONTAINER_HOST=tcp://untrusted.invalid:9999 \ -CONTAINER_CONNECTION=untrusted \ -CONTAINERS_STORAGE_CONF=/tmp/untrusted-storage.conf \ -CONTAINERS_CONF=/tmp/untrusted-containers.conf \ -CONTAINERS_REGISTRIES_CONF=/tmp/untrusted-registries.conf \ -CONTAINERS_REGISTRIES_CONF_DIR=/tmp/untrusted-registries.d \ -CONTAINERS_POLICY=/tmp/untrusted-policy.json \ -PODMAN_CONNECTIONS_CONF=/tmp/untrusted-connections.json \ -DOCKER_HOST=tcp://untrusted.invalid:2375 \ -OPENSHELL_SANDBOX_IMAGE=untrusted.invalid/sandbox:latest \ -OPENSHELL_SANDBOX_RUNTIME_IMAGE=untrusted.invalid/runtime:latest \ -OPENSHELL_GRPC_ENDPOINT=http://untrusted.invalid:1 \ -OPENSHELL_PODMAN_HOST_GATEWAY_IP=192.0.2.1 \ -OPENSHELL_PODMAN_USERNS=keep-id \ -OPENSHELL_PROVIDER_SPIFFE_WORKLOAD_API_SOCKET=/tmp/untrusted-spiffe.sock \ -OPENSHELL_E2E_PROVIDER_SPIFFE_SOCKET=/tmp/untrusted-e2e-spiffe.sock \ -OPENSHELL_APP_ARMOR_PROFILE=Unconfined \ -OPENSHELL_SANDBOX_HTTPS_PROXY=http://untrusted.invalid:8080 \ -OPENSHELL_SANDBOX_NO_PROXY=untrusted.invalid \ -OPENSHELL_SANDBOX_PROXY_AUTH_FILE=/tmp/untrusted-proxy-auth \ -OPENSHELL_SANDBOX_PROXY_AUTH_ALLOW_INSECURE=true \ -OPENSHELL_SANDBOX_PROXY_CONNECT_BY_HOSTNAME=true \ -OPENSHELL_SANDBOX_PROXY_CA_BUNDLE=/tmp/untrusted-proxy-ca \ -OPENSHELL_OTLP_ENDPOINT=http://untrusted.invalid:4317 \ -OPENSHELL_GATEWAY_NAME=untrusted \ -OPENSHELL_COMPUTE_DRIVER_BIND=192.0.2.2:50061 \ - run_harness -assert_contains "${WORKDIR}/calls" "baseline|1|${WORKDIR}/results/artifacts/baseline/gateway|${WORKDIR}/results/artifacts/baseline/cli|${WORKDIR}/results/artifacts/baseline/conformance" -assert_contains "${WORKDIR}/calls" "candidate|2|${WORKDIR}/results/artifacts/candidate/gateway|${WORKDIR}/results/artifacts/candidate/cli|${WORKDIR}/results/artifacts/candidate/conformance" -assert_contains "${WORKDIR}/calls" "|${ROOT}" -[ "$(sed -n '1s/|.*//p' "${WORKDIR}/calls")" = baseline ] || fail 'baseline was not invoked first' -[ "$(sed -n '2s/|.*//p' "${WORKDIR}/calls")" = candidate ] || fail 'candidate was not invoked second' -assert_contains "${WORKDIR}/results/baseline.json" "\"source_sha\":\"${HEAD_SHA}\"" -assert_contains "${WORKDIR}/results/baseline.json" '"schema_version":1' -assert_contains "${WORKDIR}/results/candidate.json" '"schema_version":2' -assert_contains "${WORKDIR}/results/candidate.json" "\"source_sha\":\"${HEAD_SHA}\"" -assert_contains "${WORKDIR}/results/candidate.json" '"success":true' -assert_contains "${WORKDIR}/results/comparison.json" '"parity":true' -assert_contains "${WORKDIR}/results/semantic-verification.json" '"accepted": true' -assert_not_contains "${WORKDIR}/results/baseline.json" '"scenarios"' -assert_contains "${WORKDIR}/results/baseline.log" '"scenarios"' -assert_contains "${WORKDIR}/results/baseline.conformance.json" '"passed":true' -assert_contains "${WORKDIR}/podman-calls" 'pull nvcr.io/nvidia/base/ubuntu:24.04' -assert_contains "${WORKDIR}/podman-calls" "pull ${TEST_SUPERVISOR_BASE}" -assert_contains "${WORKDIR}/podman-calls" 'unshare rm -rf -- ' -assert_contains "${WORKDIR}/podman-calls" 'openshell-parity-run.' - -set +e -OPENSHELL_PARITY_TEST_INVALID_REPORT=1 run_harness >"${WORKDIR}/invalid-report.out" 2>&1 -status=$? -set -e -assert_status "${status}" 1 -assert_contains "${WORKDIR}/invalid-report.out" 'conformance report did not pass' -assert_contains "${WORKDIR}/invalid-report.out" 'semantic verification failed for smoke' -assert_contains "${WORKDIR}/results/comparison.json" '"classification":"regression"' -assert_contains "${WORKDIR}/results/comparison.json" '"accepted":false' - -set +e -OPENSHELL_E2E_PODMAN_SANDBOX_IMAGE=untrusted.invalid/sandbox:latest \ - run_harness >"${WORKDIR}/mutable-sandbox.out" 2>&1 -status=$? -set -e -assert_status "${status}" 2 -assert_contains "${WORKDIR}/mutable-sandbox.out" 'must be digest-pinned for parity runs' - -run_harness --scenario external-driver -assert_contains "${WORKDIR}/calls" "|1|${WORKDIR}/results/artifacts/baseline/external-driver|${WORKDIR}/results/artifacts/baseline/supervisor" -assert_contains "${WORKDIR}/calls" "|1|${WORKDIR}/results/artifacts/candidate/external-driver|${WORKDIR}/results/artifacts/candidate/supervisor" -assert_contains "${WORKDIR}/results/baseline.json" '"scenario":"external-driver"' -assert_contains "${WORKDIR}/results/baseline.json" '"command_class":"external_driver_conformance_smoke"' -assert_contains "${WORKDIR}/results/baseline.json" '"gateway_profile":"driver-free"' -assert_contains "${WORKDIR}/results/baseline.json" '"gateway_cargo_features":"--no-default-features --features telemetry"' -assert_contains "${WORKDIR}/results/baseline.json" '"gateway_origin":"supplied_override"' -assert_contains "${WORKDIR}/results/baseline.json" '"external_driver_origin":"supplied_override"' -assert_contains "${WORKDIR}/results/baseline.launch.json" '"compute_driver_transport":"remote_uds"' -assert_contains "${WORKDIR}/results/baseline.launch.json" '"external_driver_pull_policy":"missing"' -assert_contains "${WORKDIR}/results/baseline.launch.json" '"supervisor_image_digest":"sha256:' -assert_contains "${WORKDIR}/results/baseline.launch.json" '"supervisor_runtime_image":"localhost/openshell/supervisor@sha256:' -assert_contains "${WORKDIR}/results/candidate.launch.json" '"external_driver_pull_policy":"if_not_present"' -assert_contains "${WORKDIR}/results/baseline.json" '"gateway_sha256"' -assert_contains "${WORKDIR}/results/baseline.json" '"cli_sha256"' -assert_contains "${WORKDIR}/results/baseline.json" '"conformance_sha256"' -assert_contains "${WORKDIR}/results/baseline.json" '"supervisor_origin":"supplied_override"' -assert_contains "${WORKDIR}/results/baseline.json" '"supervisor_sha256"' -assert_contains "${WORKDIR}/results/baseline.json" '"supervisor_dockerfile_sha256"' -assert_contains "${WORKDIR}/results/baseline.json" '"external_driver_sha256"' -assert_contains "${WORKDIR}/results/comparison.json" '"classification":"pass"' -assert_contains "${WORKDIR}/results/semantic-verification.json" '"scenario": "external-driver"' - -set +e -OPENSHELL_PARITY_TEST_CANDIDATE_DRIVER_OVERRIDE="${WORKDIR}/bin/baseline-driver" \ - run_harness --scenario external-driver >"${WORKDIR}/same-driver.out" 2>&1 -status=$? -set -e -assert_status "${status}" 2 -assert_contains "${WORKDIR}/same-driver.out" 'requires distinct baseline and candidate driver artifacts' - -cp "${WORKDIR}/bin/baseline-driver" "${WORKDIR}/bin/same-content-driver" -set +e -OPENSHELL_PARITY_TEST_CANDIDATE_DRIVER_OVERRIDE="${WORKDIR}/bin/same-content-driver" \ - run_harness --scenario external-driver >"${WORKDIR}/same-driver-content.out" 2>&1 -status=$? -set -e -assert_status "${status}" 2 -assert_contains "${WORKDIR}/same-driver-content.out" 'requires different baseline and candidate driver content' - -run_harness --scenario podman-options -assert_contains "${WORKDIR}/calls" "baseline|1|${WORKDIR}/results/artifacts/baseline/gateway|${WORKDIR}/results/artifacts/baseline/cli|${WORKDIR}/results/artifacts/baseline/conformance|${ROOT}|podman-options" -assert_contains "${WORKDIR}/calls" "candidate|2|${WORKDIR}/results/artifacts/candidate/gateway|${WORKDIR}/results/artifacts/candidate/cli|${WORKDIR}/results/artifacts/candidate/conformance|${ROOT}|podman-options" -assert_contains "${WORKDIR}/results/baseline.json" '"scenario":"podman-options"' -assert_contains "${WORKDIR}/results/baseline.json" '"command_class":"podman_options"' -assert_contains "${WORKDIR}/results/baseline.json" '"normalized_result":"baseline.normalized.json"' -assert_contains "${WORKDIR}/results/baseline.normalized.json" '"stable":true' -assert_contains "${WORKDIR}/results/baseline.normalized.json" '"pids_limit":2048' -assert_contains "${WORKDIR}/results/candidate.normalized.json" '"pids_limit":31' -assert_not_contains "${WORKDIR}/results/baseline.json" '"scenarios"' -assert_contains "${WORKDIR}/results/baseline.log" '"scenarios"' -assert_not_contains "${WORKDIR}/results/baseline.json" 'raw output' -assert_contains "${WORKDIR}/results/comparison.json" '"scenario":"podman-options"' -assert_contains "${WORKDIR}/results/comparison.json" '"parity":false' -assert_contains "${WORKDIR}/results/comparison.json" '"classification":"intentional_change"' -assert_contains "${WORKDIR}/results/comparison.json" '"intentional_change_id":"podman-pid-limit-restored"' -assert_contains "${WORKDIR}/results/comparison.json" '"accepted":true' - -set +e -OPENSHELL_PARITY_TEST_SEMANTIC_DRIFT=1 run_harness --scenario podman-options >"${WORKDIR}/drift.out" 2>&1 -status=$? -set -e -assert_status "${status}" 1 -assert_contains "${WORKDIR}/results/comparison.json" '"classification":"regression"' -assert_contains "${WORKDIR}/results/comparison.json" '"accepted":false' - -# Refuse an existing output path instead of consuming stale evidence from it. -[ -s "${WORKDIR}/results/candidate.normalized.json" ] || fail 'stale result fixture is missing' -set +e -OPENSHELL_PARITY_TEST_KEEP_RESULTS_DIR=1 run_harness --scenario podman-options >"${WORKDIR}/stale-results.out" 2>&1 -status=$? -set -e -assert_status "${status}" 2 -assert_contains "${WORKDIR}/stale-results.out" 'parity results directory already exists' - -# A fresh run whose wrapper emits no candidate result must fail. -set +e -OPENSHELL_PARITY_TEST_SKIP_RESULT=candidate run_harness --scenario podman-options >"${WORKDIR}/missing-result.out" 2>&1 -status=$? -set -e -assert_status "${status}" 1 -assert_contains "${WORKDIR}/results/comparison.json" '"classification":"regression"' -assert_contains "${WORKDIR}/results/comparison.json" '"accepted":false' - -set +e -OPENSHELL_PARITY_FAIL_VARIANT=both run_harness >"${WORKDIR}/failure.out" 2>&1 -status=$? -set -e -assert_status "${status}" 1 -assert_contains "${WORKDIR}/results/baseline.json" '"success":false' -assert_contains "${WORKDIR}/results/candidate.json" '"success":false' -assert_contains "${WORKDIR}/results/comparison.json" '"parity":false' -[ "$(wc -l <"${WORKDIR}/calls")" -eq 14 ] || fail 'candidate did not run after baseline failure' - -set +e -OPENSHELL_PARITY_TEST_MUTATE_ARTIFACT=candidate run_harness >"${WORKDIR}/mutation.out" 2>&1 -status=$? -set -e -assert_status "${status}" 1 -assert_contains "${WORKDIR}/mutation.out" 'candidate gateway changed after it was staged for execution' -assert_contains "${WORKDIR}/results/candidate.json" '"success":false' -assert_contains "${WORKDIR}/results/comparison.json" '"classification":"regression"' - -set +e -bash "${ROOT}/e2e/parity/run.sh" --driver docker >"${WORKDIR}/driver.out" 2>&1 -status=$? -set -e -assert_status "${status}" 2 -assert_contains "${WORKDIR}/driver.out" 'only --driver podman is supported' - -set +e -bash "${ROOT}/e2e/parity/run.sh" --driver >"${WORKDIR}/option.out" 2>&1 -status=$? -set -e -assert_status "${status}" 2 -assert_contains "${WORKDIR}/option.out" '--driver requires a value' - -echo 'e2e parity deterministic tests passed.' diff --git a/e2e/parity/trace-cli.sh b/e2e/parity/trace-cli.sh deleted file mode 100755 index 372c1b1d3a..0000000000 --- a/e2e/parity/trace-cli.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Transparently invoke the staged OpenShell CLI while retaining the exact -# stdout bytes produced by the conformance exec probe. - -set -uo pipefail - -: "${OPENSHELL_PARITY_REAL_CLI:?OPENSHELL_PARITY_REAL_CLI is required}" -: "${OPENSHELL_PARITY_EXEC_STDOUT_CAPTURE:?OPENSHELL_PARITY_EXEC_STDOUT_CAPTURE is required}" - -tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/openshell-parity-cli.XXXXXX")" -cleanup() { - rm -rf "${tmpdir}" -} -trap cleanup EXIT - -set +e -"${OPENSHELL_PARITY_REAL_CLI}" "$@" >"${tmpdir}/stdout" 2>"${tmpdir}/stderr" -status=$? -set -e - -cat "${tmpdir}/stdout" -cat "${tmpdir}/stderr" >&2 - -if [ "${1:-}" = sandbox ] && [ "${2:-}" = exec ]; then - install -m 0444 "${tmpdir}/stdout" "${OPENSHELL_PARITY_EXEC_STDOUT_CAPTURE}" -fi - -exit "${status}" diff --git a/e2e/parity/trace-conformance.sh b/e2e/parity/trace-conformance.sh deleted file mode 100755 index c02172a5b9..0000000000 --- a/e2e/parity/trace-conformance.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Retain the conformance runner's stdout as one parseable JSON document while -# forwarding it unchanged to the parity run log. Stderr remains live so -# lifecycle diagnostics and the run ID continue to appear in the raw evidence. - -set -euo pipefail - -REAL_CONFORMANCE="${OPENSHELL_PARITY_REAL_CONFORMANCE:?OPENSHELL_PARITY_REAL_CONFORMANCE is required}" -CAPTURE="${OPENSHELL_PARITY_CONFORMANCE_REPORT_CAPTURE:?OPENSHELL_PARITY_CONFORMANCE_REPORT_CAPTURE is required}" - -if [ ! -x "${REAL_CONFORMANCE}" ]; then - echo "ERROR: real conformance binary is not executable: ${REAL_CONFORMANCE}" >&2 - exit 2 -fi - -mkdir -p "$(dirname "${CAPTURE}")" -temporary="$(mktemp "${CAPTURE}.tmp.XXXXXX")" -cleanup() { - rm -f -- "${temporary}" -} -trap cleanup EXIT - -set +e -"${REAL_CONFORMANCE}" "$@" | tee "${temporary}" -statuses=("${PIPESTATUS[@]}") -set -e -status=${statuses[0]} -if [ "${statuses[1]}" -ne 0 ]; then - echo "ERROR: could not retain conformance stdout: ${CAPTURE}" >&2 - status=${statuses[1]} -else - mv -f -- "${temporary}" "${CAPTURE}" -fi -exit "${status}" diff --git a/e2e/parity/verify-results.py b/e2e/parity/verify-results.py deleted file mode 100644 index dbe1fc9754..0000000000 --- a/e2e/parity/verify-results.py +++ /dev/null @@ -1,839 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Verify retained parity evidence and emit its normalized comparison.""" - -from __future__ import annotations - -import argparse -import hashlib -import json -import re -import tomllib -from pathlib import Path -from typing import Any - -SHA256_RE = re.compile(r"^[0-9a-f]{64}$") -DIGEST_REFERENCE_RE = re.compile(r"^[^@]+@sha256:([0-9a-f]{64})$") -ARTIFACT_FIELDS = { - "gateway_sha256": "gateway", - "cli_sha256": "cli", - "conformance_sha256": "conformance", - "supervisor_sha256": "supervisor", - "supervisor_dockerfile_sha256": "supervisor.Dockerfile", - "cli_trace_wrapper_sha256": "cli-trace-wrapper", -} -ORACLE_MARKERS = ( - "][smoke/status] completed", - "][smoke/create] completed", - "][smoke/get-ready] completed", - "][smoke/list-visible/0] completed", - "][smoke/exec] completed", - "][smoke/delete] completed", - "][smoke/list-empty/query/0] completed", -) - - -def sha256(path: Path) -> str: - digest = hashlib.sha256() - with path.open("rb") as source: - for chunk in iter(lambda: source.read(1024 * 1024), b""): - digest.update(chunk) - return digest.hexdigest() - - -def load_json(path: Path) -> dict[str, Any]: - with path.open(encoding="utf-8") as source: - value = json.load(source) - if not isinstance(value, dict): - raise ValueError(f"{path}: expected a JSON object") - return value - - -def require(condition: bool, message: str) -> None: - if not condition: - raise ValueError(message) - - -def image_repository(reference: str) -> str: - """Return an OCI image repository without a tag or digest.""" - repository = reference.split("@", 1)[0] - last_slash = repository.rfind("/") - last_colon = repository.rfind(":") - if last_colon > last_slash: - repository = repository[:last_colon] - return repository - - -def verify_conformance_report(path: Path) -> None: - report = load_json(path) - require(report.get("passed") is True, f"{path}: conformance report did not pass") - scenarios = report.get("scenarios") - require( - isinstance(scenarios, list) and len(scenarios) == 1, - f"{path}: expected exactly one conformance scenario", - ) - scenario = scenarios[0] - require(isinstance(scenario, dict), f"{path}: invalid conformance scenario") - require(scenario.get("name") == "smoke", f"{path}: smoke scenario is missing") - require(scenario.get("passed") is True, f"{path}: smoke scenario did not pass") - require( - scenario.get("diagnostic") is None, - f"{path}: successful smoke scenario retained a diagnostic", - ) - - -def verify_variant( - results_dir: Path, - variant: str, - expected_sha: str, - schema_version: int, - scenario: str, - *, - require_built_artifacts: bool = True, - require_conformance_report: bool = False, -) -> dict[str, Any]: - result_path = results_dir / f"{variant}.json" - launch_path = results_dir / f"{variant}.launch.json" - log_path = results_dir / f"{variant}.log" - config_path = results_dir / f"{variant}.gateway.toml" - result = load_json(result_path) - launch = load_json(launch_path) - require(config_path.is_file(), f"{config_path}: retained gateway config is missing") - with config_path.open("rb") as config_file: - config = tomllib.load(config_file) - - require(result.get("variant") == variant, f"{result_path}: variant mismatch") - require( - result.get("source_sha") == expected_sha, f"{result_path}: source SHA mismatch" - ) - require( - result.get("schema_version") == schema_version, - f"{result_path}: schema mismatch", - ) - require(result.get("scenario") == scenario, f"{result_path}: scenario mismatch") - require(result.get("driver") == "podman", f"{result_path}: driver mismatch") - expected_profile = "driver-free" if scenario == "external-driver" else "in-tree" - expected_features = ( - "--no-default-features --features telemetry" - if scenario == "external-driver" - else "default" - ) - require( - result.get("gateway_profile") == expected_profile, - f"{result_path}: gateway profile mismatch", - ) - require( - result.get("gateway_cargo_features") == expected_features, - f"{result_path}: gateway feature profile mismatch", - ) - require(result.get("success") is True, f"{result_path}: parity oracle did not pass") - accepted_origins = ( - {"built_by_harness"} - if require_built_artifacts - else {"built_by_harness", "supplied_override"} - ) - for field, label in ( - ("gateway_origin", "gateway"), - ("cli_origin", "CLI"), - ("conformance_origin", "conformance runner"), - ("supervisor_origin", "supervisor"), - ): - require( - result.get(field) in accepted_origins, - f"{result_path}: invalid {label} artifact origin", - ) - - external = scenario == "external-driver" - if external: - require( - result.get("external_driver_origin") in accepted_origins, - f"{result_path}: invalid external driver artifact origin", - ) - else: - require( - result.get("external_driver_origin") == "not_applicable", - f"{result_path}: external driver origin mismatch", - ) - require( - launch.get("schema_version") == schema_version, - f"{launch_path}: schema mismatch", - ) - require( - launch.get("external_compute_driver") is external, - f"{launch_path}: topology mismatch", - ) - expected_transport = "remote_uds" if external else "in_tree" - require( - launch.get("compute_driver_transport") == expected_transport, - f"{launch_path}: transport mismatch", - ) - expected_policy = "missing" if schema_version == 1 else "if_not_present" - require( - launch.get("external_driver_pull_policy") == expected_policy, - f"{launch_path}: pull-policy mismatch", - ) - - openshell = config.get("openshell", {}) - gateway = openshell.get("gateway", {}) - podman_config = openshell.get("drivers", {}).get("podman", {}) - require( - openshell.get("version") == schema_version, f"{config_path}: schema mismatch" - ) - expected_selector = ["podman"] if schema_version == 1 else "podman" - selector_field = "compute_drivers" if schema_version == 1 else "compute_driver" - require( - gateway.get(selector_field) == expected_selector, - f"{config_path}: selected compute driver mismatch", - ) - require(isinstance(podman_config, dict), f"{config_path}: Podman table is missing") - if external: - require( - set(podman_config) == {"socket_path"}, - f"{config_path}: external gateway Podman table is not transport-only", - ) - require( - isinstance(podman_config["socket_path"], str) - and Path(podman_config["socket_path"]).is_absolute(), - f"{config_path}: external driver socket path is not absolute", - ) - else: - required_runtime_fields = { - "socket_path", - "network_name", - "default_image", - "image_pull_policy", - "supervisor_image", - } - require( - set(podman_config) >= required_runtime_fields, - f"{config_path}: in-tree Podman runtime fields are incomplete", - ) - require( - DIGEST_REFERENCE_RE.fullmatch(podman_config["default_image"]) is not None, - f"{config_path}: sandbox image is not digest-pinned", - ) - require( - DIGEST_REFERENCE_RE.fullmatch(podman_config["supervisor_image"]) - is not None, - f"{config_path}: supervisor image is not digest-pinned", - ) - - artifact_fields = dict(ARTIFACT_FIELDS) - if external: - artifact_fields["external_driver_sha256"] = "external-driver" - artifact_hashes: dict[str, str] = {} - for field, filename in artifact_fields.items(): - expected_hash = result.get(field) - require( - isinstance(expected_hash, str) - and SHA256_RE.fullmatch(expected_hash) is not None, - f"{result_path}: invalid {field}", - ) - artifact_path = results_dir / "artifacts" / variant / filename - require( - artifact_path.is_file(), f"{artifact_path}: retained artifact is missing" - ) - actual_hash = sha256(artifact_path) - require( - actual_hash == expected_hash, - f"{artifact_path}: retained artifact hash mismatch", - ) - artifact_hashes[filename] = actual_hash - - image_id = launch.get("supervisor_image_id") - image_digest = launch.get("supervisor_image_digest") - runtime_image = launch.get("supervisor_runtime_image") - require( - isinstance(image_id, str) and SHA256_RE.fullmatch(image_id) is not None, - f"{launch_path}: invalid supervisor image ID", - ) - require( - isinstance(image_digest, str) - and re.fullmatch(r"sha256:[0-9a-f]{64}", image_digest) is not None, - f"{launch_path}: invalid supervisor image digest", - ) - match = ( - DIGEST_REFERENCE_RE.fullmatch(runtime_image) - if isinstance(runtime_image, str) - else None - ) - require( - match is not None, - f"{launch_path}: supervisor runtime image is not digest-pinned", - ) - require( - f"sha256:{match.group(1)}" == image_digest, - f"{launch_path}: runtime reference digest mismatch", - ) - - sandbox_request = launch.get("sandbox_image_request") - sandbox_id = launch.get("sandbox_image_id") - sandbox_digest = launch.get("sandbox_image_digest") - sandbox_runtime = launch.get("sandbox_runtime_image") - sandbox_boundary_image = launch.get("sandbox_boundary_image") - sandbox_match = ( - DIGEST_REFERENCE_RE.fullmatch(sandbox_runtime) - if isinstance(sandbox_runtime, str) - else None - ) - require( - isinstance(sandbox_id, str) and SHA256_RE.fullmatch(sandbox_id) is not None, - f"{launch_path}: invalid sandbox image ID", - ) - require( - isinstance(sandbox_digest, str) - and re.fullmatch(r"sha256:[0-9a-f]{64}", sandbox_digest) is not None, - f"{launch_path}: invalid sandbox image digest", - ) - require( - sandbox_match is not None - and f"sha256:{sandbox_match.group(1)}" == sandbox_digest, - f"{launch_path}: sandbox runtime image is not digest-pinned", - ) - require( - sandbox_request == sandbox_runtime, - f"{launch_path}: sandbox image request was not the resolved digest reference", - ) - require( - isinstance(sandbox_boundary_image, str) and sandbox_boundary_image, - f"{launch_path}: sandbox boundary image is missing", - ) - if not external: - require( - podman_config["default_image"] == sandbox_runtime - and podman_config["supervisor_image"] == runtime_image, - f"{config_path}: runtime image references differ from launch evidence", - ) - - base_runtime = launch.get("supervisor_base_runtime_image") - base_runtime_match = ( - DIGEST_REFERENCE_RE.fullmatch(base_runtime) - if isinstance(base_runtime, str) - else None - ) - require( - base_runtime_match is not None, - f"{launch_path}: supervisor base runtime image is not digest-pinned", - ) - for field in ("supervisor_base_image_id", "supervisor_package_manifest_sha256"): - value = launch.get(field) - require( - isinstance(value, str) and SHA256_RE.fullmatch(value) is not None, - f"{launch_path}: invalid {field}", - ) - base_digest = launch.get("supervisor_base_image_digest") - require( - isinstance(base_digest, str) - and re.fullmatch(r"sha256:[0-9a-f]{64}", base_digest) is not None, - f"{launch_path}: invalid supervisor base-image digest", - ) - package_path = results_dir / "artifacts" / variant / "supervisor.packages.txt" - require(package_path.is_file(), f"{package_path}: package manifest is missing") - require( - sha256(package_path) == launch["supervisor_package_manifest_sha256"], - f"{package_path}: package manifest hash mismatch", - ) - artifact_hashes["supervisor.packages.txt"] = sha256(package_path) - - sandbox_alias = launch.get("sandbox_client_image_alias") - require( - isinstance(sandbox_alias, str) - and "@" not in sandbox_alias - and image_repository(sandbox_alias) == image_repository(sandbox_runtime) - and launch.get("sandbox_client_image_alias_id") == sandbox_id, - f"{launch_path}: sandbox client alias is not bound to the pinned image", - ) - - for launch_field, result_field in ( - ("gateway_sha256_before_execution", "gateway_sha256"), - ("cli_sha256_before_execution", "cli_sha256"), - ("conformance_sha256_before_execution", "conformance_sha256"), - ("supervisor_sha256_before_execution", "supervisor_sha256"), - ( - "supervisor_dockerfile_sha256_before_execution", - "supervisor_dockerfile_sha256", - ), - ("cli_trace_wrapper_sha256_before_execution", "cli_trace_wrapper_sha256"), - ): - require( - launch.get(launch_field) == result.get(result_field), - f"{launch_path}: {launch_field} does not bind the staged artifact", - ) - if external: - require( - launch.get("external_driver_sha256_before_execution") - == result.get("external_driver_sha256"), - f"{launch_path}: external driver pre-execution hash mismatch", - ) - gateway_port = launch.get("gateway_port") - grpc_endpoint = f"https://127.0.0.1:{gateway_port}" - require( - isinstance(gateway_port, int) - and 0 < gateway_port <= 65535 - and launch.get("external_driver_grpc_endpoint") == grpc_endpoint, - f"{launch_path}: external driver gRPC endpoint is not isolated", - ) - require( - launch.get("external_driver_host_gateway_ip") == "host-gateway" - and launch.get("external_driver_userns") is None - and launch.get("external_driver_spiffe") is False - and launch.get("external_driver_proxy") is False - and launch.get("external_driver_app_armor") is False, - f"{launch_path}: external driver effective configuration is tainted", - ) - driver_environment = launch.get("external_driver_environment") - expected_environment_keys = { - "XDG_DATA_HOME", - "OPENSHELL_COMPUTE_DRIVER_SOCKET", - "OPENSHELL_PODMAN_SOCKET", - "OPENSHELL_SANDBOX_IMAGE", - "OPENSHELL_SANDBOX_IMAGE_PULL_POLICY", - "OPENSHELL_HEALTH_CHECK_INTERVAL_SECS", - "OPENSHELL_GRPC_ENDPOINT", - "OPENSHELL_GATEWAY_PORT", - "OPENSHELL_NETWORK_NAME", - "OPENSHELL_STOP_TIMEOUT", - "OPENSHELL_SANDBOX_RUNTIME_IMAGE", - "OPENSHELL_SUPERVISOR_IMAGE", - "OPENSHELL_PODMAN_TLS_CA", - "OPENSHELL_ENABLE_BIND_MOUNTS", - } - tls_fields = {"OPENSHELL_PODMAN_TLS_CA"} - if schema_version == 1: - tls_fields.update({"OPENSHELL_PODMAN_TLS_CERT", "OPENSHELL_PODMAN_TLS_KEY"}) - expected_environment_keys.update(tls_fields) - require( - isinstance(driver_environment, dict) - and set(driver_environment) == expected_environment_keys, - f"{launch_path}: external driver allowlisted environment is incomplete", - ) - require( - Path(driver_environment["XDG_DATA_HOME"]).is_absolute(), - f"{launch_path}: external driver data directory is not absolute", - ) - require( - driver_environment["OPENSHELL_COMPUTE_DRIVER_SOCKET"] - == podman_config["socket_path"], - f"{launch_path}: external driver socket differs from gateway TOML", - ) - podman_socket = driver_environment["OPENSHELL_PODMAN_SOCKET"] - require( - isinstance(podman_socket, str) - and Path(podman_socket).is_absolute() - and podman_socket != podman_config["socket_path"], - f"{launch_path}: external driver Podman socket is not isolated", - ) - require( - driver_environment["OPENSHELL_SANDBOX_IMAGE"] == sandbox_request - and driver_environment["OPENSHELL_SANDBOX_IMAGE_PULL_POLICY"] - == expected_policy - and driver_environment["OPENSHELL_HEALTH_CHECK_INTERVAL_SECS"] == 10 - and driver_environment["OPENSHELL_GRPC_ENDPOINT"] == grpc_endpoint - and driver_environment["OPENSHELL_GATEWAY_PORT"] == gateway_port - and isinstance(driver_environment["OPENSHELL_NETWORK_NAME"], str) - and driver_environment["OPENSHELL_NETWORK_NAME"] - and isinstance(driver_environment["OPENSHELL_STOP_TIMEOUT"], int) - and driver_environment["OPENSHELL_STOP_TIMEOUT"] >= 0 - and driver_environment["OPENSHELL_SANDBOX_RUNTIME_IMAGE"] - == sandbox_boundary_image - and driver_environment["OPENSHELL_SUPERVISOR_IMAGE"] == runtime_image - and driver_environment["OPENSHELL_ENABLE_BIND_MOUNTS"] is True, - f"{launch_path}: external driver allowlisted runtime inputs differ", - ) - tls_paths: set[str] = set() - for field in tls_fields: - tls_input = driver_environment[field] - require( - isinstance(tls_input, dict) - and set(tls_input) == {"path", "sha256"} - and isinstance(tls_input["path"], str) - and Path(tls_input["path"]).is_absolute() - and isinstance(tls_input["sha256"], str) - and SHA256_RE.fullmatch(tls_input["sha256"]) is not None, - f"{launch_path}: invalid external driver TLS input {field}", - ) - tls_paths.add(tls_input["path"]) - require( - len(tls_paths) == len(tls_fields), - f"{launch_path}: external driver TLS paths are not distinct", - ) - else: - require( - launch.get("external_driver_sha256_before_execution") == "", - f"{launch_path}: unexpected external driver hash", - ) - - require(log_path.is_file(), f"{log_path}: retained raw log is missing") - raw_log = log_path.read_text(encoding="utf-8", errors="replace") - for marker in ORACLE_MARKERS: - require( - re.search(re.escape(marker) + r".*exit 0", raw_log) is not None, - f"{log_path}: missing successful lifecycle oracle marker {marker}", - ) - require( - '"passed": true' in raw_log, - f"{log_path}: missing successful conformance result", - ) - require( - re.search( - r"gateway preflight connected: .*authentication=authenticated(?:\n|$)", - raw_log, - ) - is not None, - f"{log_path}: authenticated gateway preflight is missing", - ) - run_ids = re.findall( - r"^CLI conformance run ID: ([a-z0-9]+)$", raw_log, re.MULTILINE - ) - require( - len(run_ids) == 1, - f"{log_path}: expected exactly one conformance run ID", - ) - exec_stdout_path = results_dir / f"{variant}.exec.stdout" - require( - exec_stdout_path.is_file(), - f"{exec_stdout_path}: retained exec stdout is missing", - ) - expected_exec_stdout = f"openshell-conformance-{run_ids[0]}\n".encode() - require( - exec_stdout_path.read_bytes() == expected_exec_stdout, - f"{exec_stdout_path}: callback exec stdout is not the exact marker", - ) - launch_markers = ( - runtime_image, - image_id, - image_digest, - sandbox_runtime, - sandbox_id, - sandbox_digest, - sandbox_alias, - launch["sandbox_client_image_alias_id"], - launch["supervisor_base_image_id"], - base_digest, - base_runtime, - launch["supervisor_package_manifest_sha256"], - ) - require( - all(marker in raw_log for marker in launch_markers), - f"{log_path}: launch provenance is absent from raw output", - ) - - conformance_report_verified = False - conformance_report_path = results_dir / f"{variant}.conformance.json" - if require_conformance_report: - verify_conformance_report(conformance_report_path) - conformance_report_verified = True - - raw_evidence_hashes = { - result_path.name: sha256(result_path), - launch_path.name: sha256(launch_path), - log_path.name: sha256(log_path), - config_path.name: sha256(config_path), - exec_stdout_path.name: sha256(exec_stdout_path), - } - if require_conformance_report: - raw_evidence_hashes[conformance_report_path.name] = sha256( - conformance_report_path - ) - if external: - driver_log_path = results_dir / f"{variant}.driver.log" - require( - driver_log_path.is_file() and driver_log_path.stat().st_size > 0, - f"{driver_log_path}: retained external driver log is missing or empty", - ) - raw_evidence_hashes[driver_log_path.name] = sha256(driver_log_path) - - verified = { - "schema_version": schema_version, - "source_sha": expected_sha, - "gateway_profile": result["gateway_profile"], - "gateway_cargo_features": result["gateway_cargo_features"], - "artifact_origins": { - "gateway": result["gateway_origin"], - "cli": result["cli_origin"], - "conformance": result["conformance_origin"], - "external_driver": result["external_driver_origin"], - "supervisor": result["supervisor_origin"], - }, - "artifact_sha256": artifact_hashes, - "launch_attestation": launch, - "raw_evidence_sha256": raw_evidence_hashes, - "artifacts_verified": True, - "raw_output_verified": True, - "success": True, - } - if conformance_report_verified: - verified["conformance_report_verified"] = True - return verified - - -def verify_topology( - results_dir: Path, - baseline_sha: str, - candidate_sha: str, - scenario: str, - *, - verify_comparison: bool = True, - require_built_artifacts: bool = True, -) -> dict[str, Any]: - comparison_path = results_dir / "comparison.json" - if verify_comparison: - comparison = load_json(comparison_path) - require( - comparison.get("scenario") == scenario, - f"{comparison_path}: scenario mismatch", - ) - for field in ("baseline_success", "candidate_success", "parity", "accepted"): - require( - comparison.get(field) is True, - f"{comparison_path}: {field} is not true", - ) - require( - comparison.get("classification") == "pass", - f"{comparison_path}: classification is not pass", - ) - - baseline = verify_variant( - results_dir, - "baseline", - baseline_sha, - 1, - scenario, - require_built_artifacts=require_built_artifacts, - require_conformance_report=not verify_comparison, - ) - candidate = verify_variant( - results_dir, - "candidate", - candidate_sha, - 2, - scenario, - require_built_artifacts=require_built_artifacts, - require_conformance_report=not verify_comparison, - ) - baseline_launch = baseline["launch_attestation"] - candidate_launch = candidate["launch_attestation"] - for field, label in ( - ("sandbox_image_id", "sandbox image ID"), - ("sandbox_image_digest", "sandbox image digest"), - ("sandbox_runtime_image", "sandbox runtime image"), - ("supervisor_base_image", "supervisor base image"), - ("supervisor_base_image_id", "supervisor base-image ID"), - ("supervisor_base_image_digest", "supervisor base-image digest"), - ("supervisor_package_manifest_sha256", "supervisor package manifest"), - ): - require( - baseline_launch.get(field) == candidate_launch.get(field), - f"baseline and candidate {label} differ", - ) - if scenario == "external-driver": - baseline_driver = results_dir / "artifacts/baseline/external-driver" - candidate_driver = results_dir / "artifacts/candidate/external-driver" - require( - baseline_driver.resolve() != candidate_driver.resolve(), - "external-driver artifacts resolve to the same path", - ) - require( - not baseline_driver.samefile(candidate_driver), - "external-driver artifacts share an inode", - ) - require( - baseline["artifact_sha256"]["external-driver"] - != candidate["artifact_sha256"]["external-driver"], - "external-driver artifacts have identical content", - ) - baseline_env = baseline_launch["external_driver_environment"] - candidate_env = candidate_launch["external_driver_environment"] - for field, label in ( - ("OPENSHELL_COMPUTE_DRIVER_SOCKET", "compute-driver UDS"), - ("OPENSHELL_PODMAN_SOCKET", "Podman API UDS"), - ("OPENSHELL_NETWORK_NAME", "Podman network"), - ): - require( - baseline_env[field] != candidate_env[field], - f"baseline and candidate reuse the same external {label}", - ) - for field in ( - "OPENSHELL_PODMAN_TLS_CA", - ): - require( - baseline_env[field]["path"] != candidate_env[field]["path"], - "baseline and candidate reuse the same external callback TLS path", - ) - - return { - "baseline": baseline, - "candidate": candidate, - "comparison_sha256": sha256(comparison_path) if verify_comparison else None, - "classification": "pass", - "parity": True, - "accepted": True, - } - - -def verify_four_run_provenance( - in_tree: dict[str, Any], external_uds: dict[str, Any] -) -> None: - variants = [ - in_tree["baseline"]["launch_attestation"], - in_tree["candidate"]["launch_attestation"], - external_uds["baseline"]["launch_attestation"], - external_uds["candidate"]["launch_attestation"], - ] - for fields, label in ( - ( - ( - "sandbox_image_id", - "sandbox_image_digest", - "sandbox_runtime_image", - "sandbox_client_image_alias", - "sandbox_client_image_alias_id", - ), - "sandbox artifact", - ), - ( - ( - "supervisor_base_image", - "supervisor_base_image_id", - "supervisor_base_image_digest", - "supervisor_base_runtime_image", - "supervisor_package_manifest_sha256", - ), - "supervisor dependency provenance", - ), - ): - tuples = {tuple(launch.get(field) for field in fields) for launch in variants} - require(len(tuples) == 1, f"the four runs use different {label}") - - for variant in ("baseline", "candidate"): - in_tree_artifacts = in_tree[variant]["artifact_sha256"] - external_artifacts = external_uds[variant]["artifact_sha256"] - for artifact in ( - "cli", - "conformance", - "supervisor", - "supervisor.Dockerfile", - "cli-trace-wrapper", - ): - require( - in_tree_artifacts[artifact] == external_artifacts[artifact], - f"{variant} {artifact} differs across topologies", - ) - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser() - parser.add_argument("--baseline-sha", required=True) - parser.add_argument("--candidate-sha", required=True) - parser.add_argument("--in-tree", type=Path) - parser.add_argument("--external-uds", type=Path) - parser.add_argument("--scenario", choices=("smoke", "external-driver")) - parser.add_argument("--results-dir", type=Path) - parser.add_argument("--output", required=True, type=Path) - return parser.parse_args() - - -def main() -> None: - args = parse_args() - require( - re.fullmatch(r"[0-9a-f]{40}", args.baseline_sha) is not None, - "invalid baseline SHA", - ) - require( - re.fullmatch(r"[0-9a-f]{40}", args.candidate_sha) is not None, - "invalid candidate SHA", - ) - - scenario_mode = args.scenario is not None or args.results_dir is not None - if scenario_mode: - require( - args.scenario is not None and args.results_dir is not None, - "--scenario and --results-dir must be provided together", - ) - require( - args.in_tree is None and args.external_uds is None, - "single-scenario verification cannot use --in-tree or --external-uds", - ) - topology = verify_topology( - args.results_dir, - args.baseline_sha, - args.candidate_sha, - args.scenario, - verify_comparison=False, - require_built_artifacts=False, - ) - report = { - "manifest_version": 1, - "baseline_commit": args.baseline_sha, - "candidate_commit": args.candidate_sha, - "scenario": args.scenario, - "topology": topology, - "classification": "pass", - "accepted": True, - } - args.output.parent.mkdir(parents=True, exist_ok=True) - args.output.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") - return - - require( - args.in_tree is not None and args.external_uds is not None, - "--in-tree and --external-uds are required for four-run verification", - ) - in_tree = verify_topology( - args.in_tree, args.baseline_sha, args.candidate_sha, "smoke" - ) - external_uds = verify_topology( - args.external_uds, args.baseline_sha, args.candidate_sha, "external-driver" - ) - verify_four_run_provenance(in_tree, external_uds) - - report = { - "manifest_version": 2, - "baseline_commit": args.baseline_sha, - "candidate_commit": args.candidate_sha, - "lane": "local-linux-x86_64-rootless-podman-5.8.2", - "retained_evidence_bundles": { - "in_tree": args.in_tree.as_posix(), - "external_uds": args.external_uds.as_posix(), - }, - "oracle": { - "status": True, - "create": True, - "ready": True, - "list_visible": True, - "callback_exec_exact_marker": True, - "delete": True, - "list_empty": True, - }, - "in_tree": in_tree, - "external_uds": external_uds, - "callback_listener": { - "in_tree_baseline_exec": True, - "in_tree_candidate_exec": True, - "external_baseline_exec": True, - "external_candidate_exec": True, - "classification": "pass", - }, - "classification": "pass", - "accepted": True, - "verification": { - "retained_artifact_hashes_recomputed": True, - "raw_lifecycle_output_inspected": True, - "authenticated_preflight_verified": True, - "exact_callback_exec_stdout_verified": True, - "external_driver_allowlist_verified": True, - "external_driver_logs_retained": True, - "external_uds_isolation_verified": True, - "digest_pinned_supervisor_runtime_verified": True, - "same_immutable_sandbox_verified": True, - "supervisor_dependency_provenance_matched": True, - }, - } - args.output.parent.mkdir(parents=True, exist_ok=True) - args.output.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") - - -if __name__ == "__main__": - main() diff --git a/e2e/support/debian-package-manifest.py b/e2e/support/debian-package-manifest.py deleted file mode 100644 index 4a72025031..0000000000 --- a/e2e/support/debian-package-manifest.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Read copied Debian/distroless package metadata without executing image tools.""" - -import sys -from pathlib import Path - - -def package_manifest(root: Path) -> list[str]: - status = root / "status" - sources = [status] if status.is_file() else [] - sources.extend( - path - for path in sorted((root / "status.d").glob("*")) - if path.is_file() and not path.name.endswith(".md5sums") - ) - packages = set() - for source in sources: - for stanza in source.read_text().strip().split("\n\n"): - if not stanza.strip(): - continue - fields = dict( - line.split(": ", 1) - for line in stanza.splitlines() - if not line.startswith((" ", "\t")) and ": " in line - ) - if "Status" in fields and not fields["Status"].endswith(" ok installed"): - continue - if not {"Package", "Version", "Architecture"} <= fields.keys(): - raise ValueError(f"Incomplete package metadata in {source}") - name = fields["Package"] - if fields.get("Multi-Arch") == "same": - name += ":" + fields["Architecture"] - packages.add(f"{name}={fields['Version']}") - if not packages: - raise ValueError(f"No installed Debian packages found in {root}") - return sorted(packages) - - -if __name__ == "__main__": - print("\n".join(package_manifest(Path(sys.argv[1])))) diff --git a/e2e/support/podman-gateway-config.sh b/e2e/support/podman-gateway-config.sh index e09a208ed7..ba425f91ec 100755 --- a/e2e/support/podman-gateway-config.sh +++ b/e2e/support/podman-gateway-config.sh @@ -2,30 +2,10 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -# Schema-aware Podman gateway configuration generation shared by the local e2e -# wrapper and schema parity harness. This file expects gateway-common.sh to +# Podman gateway configuration generation shared by the local e2e wrapper and +# its deterministic configuration test. This file expects gateway-common.sh to # have been sourced first. -e2e_podman_config_schema_version() { - local version="${OPENSHELL_E2E_CONFIG_SCHEMA_VERSION:-2}" - - case "${version}" in - 1|2) printf '%s\n' "${version}" ;; - *) - echo "ERROR: OPENSHELL_E2E_CONFIG_SCHEMA_VERSION must be 1 or 2 (got ${version})." >&2 - return 2 - ;; - esac -} - -e2e_podman_external_driver_pull_policy() { - case "$1" in - 1) printf '%s\n' missing ;; - 2) printf '%s\n' if_not_present ;; - *) echo "ERROR: unsupported Podman config schema version: $1" >&2; return 2 ;; - esac -} - e2e_podman_toml_string() { local value="$1" value="${value//\\/\\\\}" @@ -33,158 +13,75 @@ e2e_podman_toml_string() { printf '"%s"' "${value}" } -# Return the explicitly selected behavioral profile. Keep the default empty so -# ordinary smoke coverage continues to exercise the minimal configuration. -e2e_podman_option_profile() { - case "${OPENSHELL_E2E_PODMAN_OPTION_PROFILE:-}" in - "") printf '%s\n' "" ;; - podman-options) printf '%s\n' "podman-options" ;; - *) - echo "ERROR: unsupported OPENSHELL_E2E_PODMAN_OPTION_PROFILE: ${OPENSHELL_E2E_PODMAN_OPTION_PROFILE}" >&2 - return 2 - ;; - esac -} - -# Frozen baseline 74960ebfaeec4673885089ed995fad902459749f does not accept -# Podman app_armor_profile, so it is deliberately not part of this paired profile. - -# Write the minimally configured Podman e2e gateway TOML. The schema-v1 -# branch deliberately uses the frozen-main contract: list driver selection, -# driver-local guest TLS, the old "missing" pull-policy spelling, and zero to -# disable Podman health checks. Schema v2 uses gateway-owned guest TLS and the -# current positive health-check setting from the RPM template. +# Write the current Podman e2e gateway TOML. The RPM template opens the Podman +# driver table, so gateway-owned TLS is inserted before that table and the +# remaining driver options are appended to it. e2e_write_podman_gateway_config() { local output=$1 - local schema_version=$2 - local root=$3 - local pki_dir=$4 - local jwt_dir=$5 - local gateway_id=$6 - local external_driver=$7 - local driver_socket=$8 - local network_name=$9 - local gateway_port=${10} - local sandbox_image=${11} - local stop_timeout_secs=${12} - local supervisor_image=${13} - local sandbox_runtime_image=${14} - local provider_spiffe_socket=${15} - local podman_socket=${16} - local oidc_mode=${17} - local oidc_issuer=${18} - local configured_with_tls option_profile + local root=$2 + local pki_dir=$3 + local jwt_dir=$4 + local gateway_id=$5 + local external_driver=$6 + local driver_socket=$7 + local network_name=$8 + local gateway_port=$9 + local sandbox_image=${10} + local stop_timeout_secs=${11} + local supervisor_image=${12} + local sandbox_runtime_image=${13} + local provider_spiffe_socket=${14} + local podman_socket=${15} + local oidc_mode=${16} + local oidc_issuer=${17} + local configured_with_tls - case "${OPENSHELL_E2E_PODMAN_OPTION_PROFILE:-}" in - ""|podman-options) option_profile="${OPENSHELL_E2E_PODMAN_OPTION_PROFILE:-}" ;; - *) echo "ERROR: unsupported OPENSHELL_E2E_PODMAN_OPTION_PROFILE: ${OPENSHELL_E2E_PODMAN_OPTION_PROFILE}" >&2; return 2 ;; - esac + cp "${root}/deploy/rpm/gateway.toml.default" "${output}" + if [ "${external_driver}" = "1" ]; then + # A remote UDS driver owns all runtime options. Keep the selected gateway + # table transport-only so in-tree settings cannot be mistaken for external + # driver configuration. + sed '/^health_check_interval_secs = /d' "${output}" >"${output}.updated" + mv "${output}.updated" "${output}" + fi - case "${schema_version}" in - 1) - cp "${root}/deploy/rpm/gateway.toml.default.v1" "${output}" - { - e2e_write_gateway_jwt_config "${jwt_dir}" "${gateway_id}" - if [ "${oidc_mode}" != "1" ]; then - e2e_write_gateway_mtls_auth_config - if [ -n "${oidc_issuer}" ]; then - e2e_write_gateway_oidc_config "${oidc_issuer}" - fi - fi - printf '\n[openshell.drivers.podman]\n' - if [ "${external_driver}" = "1" ]; then - printf 'socket_path = %s\n' "$(e2e_podman_toml_string "${driver_socket}")" - else - printf 'network_name = %s\n' "$(e2e_podman_toml_string "${network_name}")" - printf 'gateway_port = %s\n' "${gateway_port}" - printf 'default_image = %s\n' "$(e2e_podman_toml_string "${sandbox_image}")" - printf 'image_pull_policy = "missing"\n' - if [ "${option_profile}" = "podman-options" ]; then - printf 'sandbox_pids_limit = 31\n' - printf 'health_check_interval_secs = 7\n' + configured_with_tls="${output}.tls" + while IFS= read -r line; do + if [ "${line}" = "[openshell.drivers.podman]" ]; then + printf 'guest_tls_ca = %s\n' "$(e2e_podman_toml_string "${pki_dir}/ca.crt")" + fi + printf '%s\n' "${line}" + done <"${output}" >"${configured_with_tls}" + mv "${configured_with_tls}" "${output}" - printf 'sandbox_ssh_socket_path = "/run/openshell/parity-ssh.sock"\n' - else - # In schema v1, zero explicitly disables Podman health checks. - printf 'health_check_interval_secs = 0\n' - fi - printf 'stop_timeout_secs = %s\n' "${stop_timeout_secs}" - printf 'supervisor_image = %s\n' "$(e2e_podman_toml_string "${supervisor_image}")" - printf 'sandbox_runtime_image = %s\n' "$(e2e_podman_toml_string "${sandbox_runtime_image}")" - printf 'guest_tls_ca = %s\n' "$(e2e_podman_toml_string "${pki_dir}/ca.crt")" - printf 'guest_tls_cert = %s\n' "$(e2e_podman_toml_string "${pki_dir}/client/tls.crt")" - printf 'guest_tls_key = %s\n' "$(e2e_podman_toml_string "${pki_dir}/client/tls.key")" - printf 'enable_bind_mounts = true\n' - if [ -n "${provider_spiffe_socket}" ]; then - printf 'provider_spiffe_workload_api_socket = %s\n' "$(e2e_podman_toml_string "${provider_spiffe_socket}")" - fi - if [ -n "${podman_socket}" ]; then - printf 'socket_path = %s\n' "$(e2e_podman_toml_string "${podman_socket}")" - fi - fi - } >>"${output}" - ;; - 2) - cp "${root}/deploy/rpm/gateway.toml.default" "${output}" - if [ "${external_driver}" = "1" ]; then - # A remote UDS driver owns all runtime options. Keep the selected - # gateway table transport-only so no in-tree setting can be mistaken - # for executed external-driver configuration. - sed '/^health_check_interval_secs = /d' "${output}" >"${output}.updated" - mv "${output}.updated" "${output}" - elif [ "${option_profile}" = "podman-options" ]; then - sed 's/^health_check_interval_secs = .*/health_check_interval_secs = 7/' \ - "${output}" >"${output}.updated" - mv "${output}.updated" "${output}" + { + if [ "${external_driver}" = "1" ]; then + printf 'socket_path = %s\n' "$(e2e_podman_toml_string "${driver_socket}")" + else + printf 'allow_driver_config = true\n' + printf 'network_name = %s\n' "$(e2e_podman_toml_string "${network_name}")" + printf 'gateway_port = %s\n' "${gateway_port}" + printf 'default_image = %s\n' "$(e2e_podman_toml_string "${sandbox_image}")" + printf 'image_pull_policy = "if_not_present"\n' + printf 'stop_timeout_secs = %s\n' "${stop_timeout_secs}" + printf 'supervisor_image = %s\n' "$(e2e_podman_toml_string "${supervisor_image}")" + printf 'sandbox_runtime_image = %s\n' "$(e2e_podman_toml_string "${sandbox_runtime_image}")" + printf 'enable_bind_mounts = true\n' + if [ -n "${provider_spiffe_socket}" ]; then + printf 'provider_spiffe_workload_api_socket = %s\n' "$(e2e_podman_toml_string "${provider_spiffe_socket}")" + fi + if [ -n "${podman_socket}" ]; then + printf 'socket_path = %s\n' "$(e2e_podman_toml_string "${podman_socket}")" + fi + printf '\n[openshell.drivers.podman.resource_admission]\n' + printf 'enabled = false\n' + fi + e2e_write_gateway_jwt_config "${jwt_dir}" "${gateway_id}" + if [ "${oidc_mode}" != "1" ]; then + e2e_write_gateway_mtls_auth_config + if [ -n "${oidc_issuer}" ]; then + e2e_write_gateway_oidc_config "${oidc_issuer}" fi - # The v2 template opens the Podman table. Insert gateway-owned TLS - # before it rather than reopening [openshell.gateway] later. - configured_with_tls="${output}.tls" - while IFS= read -r line; do - if [ "${line}" = "[openshell.drivers.podman]" ]; then - printf 'guest_tls_ca = %s\n' "$(e2e_podman_toml_string "${pki_dir}/ca.crt")" - fi - printf '%s\n' "${line}" - done <"${output}" >"${configured_with_tls}" - mv "${configured_with_tls}" "${output}" - { - if [ "${external_driver}" = "1" ]; then - printf 'socket_path = %s\n' "$(e2e_podman_toml_string "${driver_socket}")" - else - printf 'allow_driver_config = true\n' - printf 'network_name = %s\n' "$(e2e_podman_toml_string "${network_name}")" - printf 'gateway_port = %s\n' "${gateway_port}" - printf 'default_image = %s\n' "$(e2e_podman_toml_string "${sandbox_image}")" - printf 'image_pull_policy = "if_not_present"\n' - if [ "${option_profile}" = "podman-options" ]; then - printf 'sandbox_pids_limit = 31\n' - printf 'ssh_socket_path = "/run/openshell/parity-ssh.sock"\n' - fi - printf 'stop_timeout_secs = %s\n' "${stop_timeout_secs}" - printf 'supervisor_image = %s\n' "$(e2e_podman_toml_string "${supervisor_image}")" - printf 'sandbox_runtime_image = %s\n' "$(e2e_podman_toml_string "${sandbox_runtime_image}")" - printf 'enable_bind_mounts = true\n' - if [ -n "${provider_spiffe_socket}" ]; then - printf 'provider_spiffe_workload_api_socket = %s\n' "$(e2e_podman_toml_string "${provider_spiffe_socket}")" - fi - if [ -n "${podman_socket}" ]; then - printf 'socket_path = %s\n' "$(e2e_podman_toml_string "${podman_socket}")" - fi - printf '\n[openshell.drivers.podman.resource_admission]\n' - printf 'enabled = false\n' - fi - e2e_write_gateway_jwt_config "${jwt_dir}" "${gateway_id}" - if [ "${oidc_mode}" != "1" ]; then - e2e_write_gateway_mtls_auth_config - if [ -n "${oidc_issuer}" ]; then - e2e_write_gateway_oidc_config "${oidc_issuer}" - fi - fi - } >>"${output}" - ;; - *) - echo "ERROR: unsupported Podman config schema version: ${schema_version}" >&2 - return 2 - ;; - esac + fi + } >>"${output}" } diff --git a/e2e/with-podman-gateway.sh b/e2e/with-podman-gateway.sh index 20e40fce9d..57edfa0a4f 100755 --- a/e2e/with-podman-gateway.sh +++ b/e2e/with-podman-gateway.sh @@ -94,20 +94,6 @@ podman_cmd() { fi } -require_expected_sha256() { - local label=$1 path=$2 expected=$3 actual - [ -n "${expected}" ] || return 0 - if [ ! -f "${path}" ]; then - echo "ERROR: ${label} is missing before execution: ${path}" >&2 - exit 2 - fi - actual="$(sha256sum "${path}" | cut -d' ' -f1)" - if [ "${actual}" != "${expected}" ]; then - echo "ERROR: ${label} hash changed before execution." >&2 - exit 2 - fi -} - WORKDIR_PARENT="${TMPDIR:-/tmp}" WORKDIR_PARENT="${WORKDIR_PARENT%/}" WORKDIR="$(mktemp -d "${WORKDIR_PARENT}/openshell-e2e-podman.XXXXXX")" @@ -132,8 +118,7 @@ GATEWAY_PID_FILE="${WORKDIR}/gateway.pid" GATEWAY_ARGS_FILE="${WORKDIR}/gateway.args" DRIVER_BIN="" DRIVER_PID="" -DRIVER_LOG="${OPENSHELL_PARITY_EXTERNAL_DRIVER_LOG_CAPTURE:-${WORKDIR}/podman-driver.log}" -mkdir -p "$(dirname "${DRIVER_LOG}")" +DRIVER_LOG="${WORKDIR}/podman-driver.log" DRIVER_SOCKET="${WORKDIR}/compute-driver.sock" DRIVER_DATA_HOME="${WORKDIR}/driver-data" mkdir -p "${DRIVER_DATA_HOME}" @@ -158,10 +143,6 @@ export XDG_CONFIG_HOME="${WORKDIR}/config" cleanup() { local exit_code=$? - if [ -n "${SUPERVISOR_METADATA_CONTAINER:-}" ]; then - podman_cmd rm -f "${SUPERVISOR_METADATA_CONTAINER}" >/dev/null 2>&1 || true - fi - e2e_stop_gateway "${GATEWAY_PID}" "${GATEWAY_PID_FILE}" e2e_stop_process "${DRIVER_PID}" "external Podman compute driver" @@ -283,23 +264,19 @@ default_podman_socket_path() { } ensure_podman_api_socket() { - if [ "${OPENSHELL_E2E_FORCE_TEMP_PODMAN_SERVICE:-0}" != 1 ]; then - if [ -n "${OPENSHELL_PODMAN_SOCKET:-}" ]; then - export CONTAINER_HOST="${CONTAINER_HOST:-unix://${OPENSHELL_PODMAN_SOCKET}}" - return 0 - fi + if [ -n "${OPENSHELL_PODMAN_SOCKET:-}" ]; then + export CONTAINER_HOST="${CONTAINER_HOST:-unix://${OPENSHELL_PODMAN_SOCKET}}" + return 0 + fi - local default_socket - default_socket="$(default_podman_socket_path || true)" - if [ -n "${default_socket}" ] \ - && [ -S "${default_socket}" ] \ - && with_podman_config podman --url "unix://${default_socket}" info >/dev/null 2>&1; then - export OPENSHELL_PODMAN_SOCKET="${default_socket}" - export CONTAINER_HOST="${CONTAINER_HOST:-unix://${OPENSHELL_PODMAN_SOCKET}}" - return 0 - fi - else - unset OPENSHELL_PODMAN_SOCKET CONTAINER_HOST + local default_socket + default_socket="$(default_podman_socket_path || true)" + if [ -n "${default_socket}" ] \ + && [ -S "${default_socket}" ] \ + && with_podman_config podman --url "unix://${default_socket}" info >/dev/null 2>&1; then + export OPENSHELL_PODMAN_SOCKET="${default_socket}" + export CONTAINER_HOST="${CONTAINER_HOST:-unix://${OPENSHELL_PODMAN_SOCKET}}" + return 0 fi # `podman system service` is a Linux-only subcommand — the macOS client @@ -404,74 +381,6 @@ resolve_podman_sandbox_runtime_image() { ensure_podman_supervisor_image() { local image=$1 - if [ -n "${OPENSHELL_E2E_SUPERVISOR_BIN:-}" ]; then - local dockerfile=${OPENSHELL_E2E_SUPERVISOR_DOCKERFILE:-${ROOT}/deploy/docker/Dockerfile.supervisor} - local context="${WORKDIR}/supervisor-image" arch - case "${image}" in - *@*) - echo "ERROR: supplied supervisor binaries cannot be built to a digest-pinned image reference: ${image}" >&2 - echo " Use a tagged image reference when building from OPENSHELL_E2E_SUPERVISOR_BIN." >&2 - exit 2 - ;; - *:dev|*:latest) - echo "ERROR: supplied supervisor binaries require a unique versioned image tag, not ${image}." >&2 - exit 2 - ;; - *:*) ;; - *) - echo "ERROR: supplied supervisor binaries require an explicit versioned image tag: ${image}." >&2 - exit 2 - ;; - esac - case "$(uname -m)" in - x86_64|amd64) arch=amd64 ;; - aarch64|arm64) arch=arm64 ;; - *) echo "ERROR: unsupported supervisor image architecture: $(uname -m)" >&2; exit 2 ;; - esac - if [ ! -x "${OPENSHELL_E2E_SUPERVISOR_BIN}" ]; then - echo "ERROR: supplied supervisor binary is not executable: ${OPENSHELL_E2E_SUPERVISOR_BIN}" >&2 - exit 2 - fi - if [ ! -f "${dockerfile}" ]; then - echo "ERROR: supervisor Dockerfile not found: ${dockerfile}" >&2 - exit 2 - fi - require_expected_sha256 "supervisor binary" "${OPENSHELL_E2E_SUPERVISOR_BIN}" \ - "${OPENSHELL_E2E_EXPECTED_SUPERVISOR_SHA256:-}" - require_expected_sha256 "supervisor Dockerfile" "${dockerfile}" \ - "${OPENSHELL_E2E_EXPECTED_SUPERVISOR_DOCKERFILE_SHA256:-}" - mkdir -p "${context}/deploy/docker/.build/prebuilt-binaries/${arch}" - install -m 0555 "${OPENSHELL_E2E_SUPERVISOR_BIN}" \ - "${context}/deploy/docker/.build/prebuilt-binaries/${arch}/openshell-sandbox" - cp "${dockerfile}" "${context}/deploy/docker/Dockerfile.supervisor" - local -a pull_option=() - if [ -n "${OPENSHELL_E2E_SUPERVISOR_BASE_RUNTIME_IMAGE:-}" ]; then - local dockerfile_base - dockerfile_base="$(awk '$1 == "FROM" { print $2; exit }' "${dockerfile}")" - if [ "${dockerfile_base}" != "${OPENSHELL_E2E_SUPERVISOR_BASE_IMAGE:-}" ] \ - || ! [[ "${OPENSHELL_E2E_SUPERVISOR_BASE_RUNTIME_IMAGE}" =~ ^[^@]+@sha256:[0-9a-f]{64}$ ]]; then - echo "ERROR: supervisor base-image attestation does not match the Dockerfile." >&2 - exit 2 - fi - echo "Pulling pinned supervisor base image ${OPENSHELL_E2E_SUPERVISOR_BASE_RUNTIME_IMAGE}..." - podman_cmd pull "${OPENSHELL_E2E_SUPERVISOR_BASE_RUNTIME_IMAGE}" - podman_cmd tag "${OPENSHELL_E2E_SUPERVISOR_BASE_RUNTIME_IMAGE}" "${dockerfile_base}" - pull_option=(--pull=never) - fi - echo "Building Podman supervisor image ${image} from supplied binary..." - ( - cd "${context}" - podman_cmd build \ - "${pull_option[@]}" \ - --build-arg "TARGETARCH=${arch}" \ - --file deploy/docker/Dockerfile.supervisor \ - --target supervisor \ - --tag "${image}" \ - . - ) - return 0 - fi - if [ "${image}" = "openshell/supervisor:dev" ] \ && [ -z "${OPENSHELL_SUPERVISOR_IMAGE:-}" ] \ && [ -z "${CI:-}" ]; then @@ -560,12 +469,7 @@ if [ -n "${OPENSHELL_GATEWAY_ENDPOINT:-}" ]; then exit $? fi -# Validate the generated configuration dialect before creating runtime resources. -CONFIG_SCHEMA_VERSION="$(e2e_podman_config_schema_version)" -EXTERNAL_DRIVER_PULL_POLICY="$(e2e_podman_external_driver_pull_policy "${CONFIG_SCHEMA_VERSION}")" - -# Validate the opt-in profile before building images or allocating runtime resources. -e2e_podman_option_profile >/dev/null +EXTERNAL_DRIVER_PULL_POLICY="if_not_present" # Preflight for managed Podman gateway mode. if ! command -v podman >/dev/null 2>&1; then @@ -588,56 +492,7 @@ fi SUPERVISOR_IMAGE="$(resolve_podman_supervisor_image)" ensure_podman_supervisor_image "${SUPERVISOR_IMAGE}" -SUPERVISOR_IMAGE_ID="$(podman_cmd image inspect --format '{{.Id}}' "${SUPERVISOR_IMAGE}")" -SUPERVISOR_IMAGE_ID="${SUPERVISOR_IMAGE_ID#sha256:}" -SUPERVISOR_IMAGE_DIGEST="$(podman_cmd image inspect --format '{{.Digest}}' "${SUPERVISOR_IMAGE}")" -if ! [[ "${SUPERVISOR_IMAGE_ID}" =~ ^[0-9a-f]{64}$ ]]; then - echo "ERROR: could not resolve immutable supervisor image ID for ${SUPERVISOR_IMAGE}." >&2 - exit 2 -fi -if ! [[ "${SUPERVISOR_IMAGE_DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]]; then - echo "ERROR: could not resolve supervisor image digest for ${SUPERVISOR_IMAGE}." >&2 - exit 2 -fi -# The parity harness forces a temporary Podman API service into the same -# isolated XDG store where this image was built. Address the local image by its -# immutable manifest digest so policy=missing cannot resolve a mutable tag or -# contact a registry for a different artifact. -SUPERVISOR_IMAGE_REPOSITORY="${SUPERVISOR_IMAGE%%@*}" -last_component="${SUPERVISOR_IMAGE_REPOSITORY##*/}" -if [[ "${last_component}" == *:* ]]; then - SUPERVISOR_IMAGE_REPOSITORY="${SUPERVISOR_IMAGE_REPOSITORY%:*}" -fi -SUPERVISOR_RUNTIME_IMAGE="${SUPERVISOR_IMAGE_REPOSITORY}@${SUPERVISOR_IMAGE_DIGEST}" -if ! [[ "${SUPERVISOR_RUNTIME_IMAGE}" =~ ^[^@]+@sha256:[0-9a-f]{64}$ ]]; then - echo "ERROR: supervisor runtime image is not digest-pinned: ${SUPERVISOR_RUNTIME_IMAGE}" >&2 - exit 2 -fi -SUPERVISOR_BASE_IMAGE="$(awk '$1 == "FROM" { print $2; exit }' "${OPENSHELL_E2E_SUPERVISOR_DOCKERFILE:-${ROOT}/deploy/docker/Dockerfile.supervisor}")" -if ! podman_cmd image exists "${SUPERVISOR_BASE_IMAGE}" 2>/dev/null; then - echo "Pulling Podman supervisor base image ${SUPERVISOR_BASE_IMAGE}..." - podman_cmd pull "${SUPERVISOR_BASE_IMAGE}" -fi -SUPERVISOR_BASE_IMAGE_ID="$(podman_cmd image inspect --format '{{.Id}}' "${SUPERVISOR_BASE_IMAGE}")" -SUPERVISOR_BASE_IMAGE_ID="${SUPERVISOR_BASE_IMAGE_ID#sha256:}" -SUPERVISOR_BASE_IMAGE_DIGEST="$(podman_cmd image inspect --format '{{.Digest}}' "${SUPERVISOR_BASE_IMAGE}")" -if ! [[ "${SUPERVISOR_BASE_IMAGE_ID}" =~ ^[0-9a-f]{64}$ ]] \ - || ! [[ "${SUPERVISOR_BASE_IMAGE_DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]]; then - echo "ERROR: could not resolve supervisor base-image provenance for ${SUPERVISOR_BASE_IMAGE}." >&2 - exit 2 -fi -SUPERVISOR_PACKAGE_MANIFEST="${OPENSHELL_PARITY_SUPERVISOR_PACKAGE_CAPTURE:-${WORKDIR}/supervisor.packages.txt}" -mkdir -p "$(dirname "${SUPERVISOR_PACKAGE_MANIFEST}")" -# Distroless retains package metadata but has no dpkg-query executable. -# Copy from a stopped container so inventory does not execute image contents. -SUPERVISOR_METADATA_CONTAINER="$(podman_cmd create --network none "${SUPERVISOR_RUNTIME_IMAGE}")" -podman_cmd cp "${SUPERVISOR_METADATA_CONTAINER}:/var/lib/dpkg" "${WORKDIR}/supervisor-dpkg" -podman_cmd rm "${SUPERVISOR_METADATA_CONTAINER}" >/dev/null -SUPERVISOR_METADATA_CONTAINER="" -uv run --no-project python "${ROOT}/e2e/support/debian-package-manifest.py" \ - "${WORKDIR}/supervisor-dpkg" >"${SUPERVISOR_PACKAGE_MANIFEST}" -SUPERVISOR_PACKAGE_MANIFEST_SHA256="$(sha256sum "${SUPERVISOR_PACKAGE_MANIFEST}" | cut -d' ' -f1)" -echo "Using Podman supervisor image: ${SUPERVISOR_RUNTIME_IMAGE} (ID ${SUPERVISOR_IMAGE_ID}, digest ${SUPERVISOR_IMAGE_DIGEST}, base ${SUPERVISOR_BASE_IMAGE} ID ${SUPERVISOR_BASE_IMAGE_ID} digest ${SUPERVISOR_BASE_IMAGE_DIGEST}, packages ${SUPERVISOR_PACKAGE_MANIFEST_SHA256})" +echo "Using Podman supervisor image: ${SUPERVISOR_IMAGE}" SANDBOX_BOUNDARY_IMAGE="$(resolve_podman_sandbox_runtime_image)" ensure_podman_sandbox_runtime_image "${SANDBOX_BOUNDARY_IMAGE}" @@ -645,11 +500,6 @@ echo "Using Podman sandbox runtime image: ${SANDBOX_BOUNDARY_IMAGE}" DEFAULT_SANDBOX_IMAGE="nvcr.io/nvidia/base/ubuntu:24.04" SANDBOX_IMAGE_REQUEST="${OPENSHELL_E2E_PODMAN_SANDBOX_IMAGE:-${OPENSHELL_SANDBOX_IMAGE:-${DEFAULT_SANDBOX_IMAGE}}}" -if [ "${OPENSHELL_E2E_REQUIRE_DIGEST_PINNED_SANDBOX_IMAGE:-0}" = "1" ] \ - && ! [[ "${SANDBOX_IMAGE_REQUEST}" =~ ^[^@]+@sha256:[0-9a-f]{64}$ ]]; then - echo "ERROR: this e2e invocation requires a digest-pinned sandbox image: ${SANDBOX_IMAGE_REQUEST}" >&2 - exit 2 -fi PODMAN_STOP_TIMEOUT_SECS="${OPENSHELL_E2E_PODMAN_STOP_TIMEOUT_SECS:-15}" if ! [[ "${PODMAN_STOP_TIMEOUT_SECS}" =~ ^[0-9]+$ ]]; then echo "ERROR: OPENSHELL_E2E_PODMAN_STOP_TIMEOUT_SECS must be a non-negative integer." >&2 @@ -659,37 +509,7 @@ if ! podman_cmd image exists "${SANDBOX_IMAGE_REQUEST}" 2>/dev/null; then echo "Pulling ${SANDBOX_IMAGE_REQUEST}..." podman_cmd pull "${SANDBOX_IMAGE_REQUEST}" fi -SANDBOX_IMAGE_ID="$(podman_cmd image inspect --format '{{.Id}}' "${SANDBOX_IMAGE_REQUEST}")" -SANDBOX_IMAGE_ID="${SANDBOX_IMAGE_ID#sha256:}" -SANDBOX_IMAGE_DIGEST="$(podman_cmd image inspect --format '{{.Digest}}' "${SANDBOX_IMAGE_REQUEST}")" -SANDBOX_IMAGE_REPOSITORY="${SANDBOX_IMAGE_REQUEST%%@*}" -case "${SANDBOX_IMAGE_REPOSITORY##*/}" in - *:*) SANDBOX_IMAGE_REPOSITORY="${SANDBOX_IMAGE_REPOSITORY%:*}" ;; -esac -SANDBOX_RUNTIME_IMAGE="${SANDBOX_IMAGE_REPOSITORY}@${SANDBOX_IMAGE_DIGEST}" -if ! [[ "${SANDBOX_IMAGE_ID}" =~ ^[0-9a-f]{64}$ ]] \ - || ! [[ "${SANDBOX_RUNTIME_IMAGE}" =~ ^[^@]+@sha256:[0-9a-f]{64}$ ]]; then - echo "ERROR: could not resolve an immutable sandbox image for ${SANDBOX_IMAGE_REQUEST}." >&2 - exit 2 -fi -if [ "${OPENSHELL_E2E_REQUIRE_DIGEST_PINNED_SANDBOX_IMAGE:-0}" = "1" ] \ - && [ "${SANDBOX_IMAGE_REQUEST}" != "${SANDBOX_RUNTIME_IMAGE}" ]; then - echo "ERROR: sandbox image digest changed while resolving ${SANDBOX_IMAGE_REQUEST}." >&2 - exit 2 -fi -SANDBOX_CLIENT_IMAGE_ALIAS="" -SANDBOX_CLIENT_IMAGE_ALIAS_ID="" -if [ "${OPENSHELL_E2E_REQUIRE_DIGEST_PINNED_SANDBOX_IMAGE:-0}" = "1" ]; then - SANDBOX_CLIENT_IMAGE_ALIAS="${SANDBOX_IMAGE_REPOSITORY}:latest" - podman_cmd tag "${SANDBOX_RUNTIME_IMAGE}" "${SANDBOX_CLIENT_IMAGE_ALIAS}" - SANDBOX_CLIENT_IMAGE_ALIAS_ID="$(podman_cmd image inspect --format '{{.Id}}' "${SANDBOX_CLIENT_IMAGE_ALIAS}")" - SANDBOX_CLIENT_IMAGE_ALIAS_ID="${SANDBOX_CLIENT_IMAGE_ALIAS_ID#sha256:}" - if [ "${SANDBOX_CLIENT_IMAGE_ALIAS_ID}" != "${SANDBOX_IMAGE_ID}" ]; then - echo "ERROR: sandbox client alias does not resolve to the pinned sandbox image." >&2 - exit 2 - fi -fi -echo "Using Podman sandbox image: ${SANDBOX_RUNTIME_IMAGE} (ID ${SANDBOX_IMAGE_ID}, digest ${SANDBOX_IMAGE_DIGEST}, client alias ${SANDBOX_CLIENT_IMAGE_ALIAS:-none} ID ${SANDBOX_CLIENT_IMAGE_ALIAS_ID:-none})" +echo "Using Podman sandbox image: ${SANDBOX_IMAGE_REQUEST}" PKI_DIR="${WORKDIR}/pki" e2e_generate_pki "${GATEWAY_BIN}" "${PKI_DIR}" "host.containers.internal" @@ -719,7 +539,6 @@ e2e_generate_gateway_jwt "${JWT_DIR}" GATEWAY_CONFIG="${STATE_DIR}/gateway.toml" e2e_write_podman_gateway_config \ "${GATEWAY_CONFIG}" \ - "${CONFIG_SCHEMA_VERSION}" \ "${ROOT}" \ "${PKI_DIR}" \ "${JWT_DIR}" \ @@ -728,113 +547,19 @@ e2e_write_podman_gateway_config \ "${DRIVER_SOCKET}" \ "${PODMAN_NETWORK_NAME}" \ "${HOST_PORT}" \ - "${SANDBOX_RUNTIME_IMAGE}" \ + "${SANDBOX_IMAGE_REQUEST}" \ "${PODMAN_STOP_TIMEOUT_SECS}" \ - "${SUPERVISOR_RUNTIME_IMAGE}" \ + "${SUPERVISOR_IMAGE}" \ "${SANDBOX_BOUNDARY_IMAGE}" \ "${OPENSHELL_E2E_PROVIDER_SPIFFE_SOCKET:-}" \ "${OPENSHELL_PODMAN_SOCKET:-}" \ "${OIDC_MODE}" \ "${OPENSHELL_OIDC_ISSUER:-}" -if [ -n "${OPENSHELL_PARITY_GATEWAY_CONFIG_CAPTURE:-}" ]; then - cp "${GATEWAY_CONFIG}" "${OPENSHELL_PARITY_GATEWAY_CONFIG_CAPTURE}" -fi EXTERNAL_DRIVER_GRPC_ENDPOINT="https://127.0.0.1:${HOST_PORT}" EXTERNAL_DRIVER_HEALTH_CHECK_INTERVAL_SECS=10 EXTERNAL_DRIVER_ENABLE_BIND_MOUNTS=true EXTERNAL_DRIVER_TLS_CA="${PKI_DIR}/ca.crt" -EXTERNAL_DRIVER_TLS_CERT="${PKI_DIR}/client/tls.crt" -EXTERNAL_DRIVER_TLS_KEY="${PKI_DIR}/client/tls.key" -# The frozen schema-v1 baseline still requires a gateway client identity. -external_driver_legacy_tls_env=() -if [ "${CONFIG_SCHEMA_VERSION}" = "1" ]; then - external_driver_legacy_tls_env=( - "OPENSHELL_PODMAN_TLS_CERT=${EXTERNAL_DRIVER_TLS_CERT}" - "OPENSHELL_PODMAN_TLS_KEY=${EXTERNAL_DRIVER_TLS_KEY}" - ) -fi -if [ -n "${OPENSHELL_PARITY_LAUNCH_MANIFEST_CAPTURE:-}" ]; then - driver_transport=in_tree - external_driver_grpc_endpoint=null - external_driver_host_gateway_ip=null - external_driver_userns=null - external_driver_spiffe=false - external_driver_proxy=false - external_driver_app_armor=false - external_driver_environment=null - if [ "${OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER:-0}" = "1" ]; then - driver_transport=remote_uds - external_driver_grpc_endpoint="\"${EXTERNAL_DRIVER_GRPC_ENDPOINT}\"" - external_driver_host_gateway_ip='"host-gateway"' - driver_tls_ca_sha256="$(sha256sum "${EXTERNAL_DRIVER_TLS_CA}" | cut -d' ' -f1)" - legacy_tls_manifest="" - if [ "${CONFIG_SCHEMA_VERSION}" = "1" ]; then - driver_tls_cert_sha256="$(sha256sum "${EXTERNAL_DRIVER_TLS_CERT}" | cut -d' ' -f1)" - driver_tls_key_sha256="$(sha256sum "${EXTERNAL_DRIVER_TLS_KEY}" | cut -d' ' -f1)" - legacy_tls_manifest="$(printf ',"OPENSHELL_PODMAN_TLS_CERT":{"path":"%s","sha256":"%s"},"OPENSHELL_PODMAN_TLS_KEY":{"path":"%s","sha256":"%s"}' \ - "${EXTERNAL_DRIVER_TLS_CERT}" "${driver_tls_cert_sha256}" \ - "${EXTERNAL_DRIVER_TLS_KEY}" "${driver_tls_key_sha256}")" - fi - external_driver_environment="$(printf '{\"XDG_DATA_HOME\":\"%s\",\"OPENSHELL_COMPUTE_DRIVER_SOCKET\":\"%s\",\"OPENSHELL_PODMAN_SOCKET\":\"%s\",\"OPENSHELL_SANDBOX_IMAGE\":\"%s\",\"OPENSHELL_SANDBOX_IMAGE_PULL_POLICY\":\"%s\",\"OPENSHELL_HEALTH_CHECK_INTERVAL_SECS\":%s,\"OPENSHELL_GRPC_ENDPOINT\":\"%s\",\"OPENSHELL_GATEWAY_PORT\":%s,\"OPENSHELL_NETWORK_NAME\":\"%s\",\"OPENSHELL_STOP_TIMEOUT\":%s,\"OPENSHELL_SANDBOX_RUNTIME_IMAGE\":\"%s\",\"OPENSHELL_SUPERVISOR_IMAGE\":\"%s\",\"OPENSHELL_PODMAN_TLS_CA\":{\"path\":\"%s\",\"sha256\":\"%s\"}%s,\"OPENSHELL_ENABLE_BIND_MOUNTS\":%s}' \ - "${DRIVER_DATA_HOME}" \ - "${DRIVER_SOCKET}" \ - "${OPENSHELL_PODMAN_SOCKET:-}" \ - "${SANDBOX_IMAGE_REQUEST}" \ - "${EXTERNAL_DRIVER_PULL_POLICY}" \ - "${EXTERNAL_DRIVER_HEALTH_CHECK_INTERVAL_SECS}" \ - "${EXTERNAL_DRIVER_GRPC_ENDPOINT}" \ - "${HOST_PORT}" \ - "${PODMAN_NETWORK_NAME}" \ - "${PODMAN_STOP_TIMEOUT_SECS}" \ - "${SANDBOX_BOUNDARY_IMAGE}" \ - "${SUPERVISOR_RUNTIME_IMAGE}" \ - "${EXTERNAL_DRIVER_TLS_CA}" \ - "${driver_tls_ca_sha256}" \ - "${legacy_tls_manifest}" \ - "${EXTERNAL_DRIVER_ENABLE_BIND_MOUNTS}")" - fi - printf '{"schema_version":%s,"gateway_port":%s,"external_compute_driver":%s,"compute_driver_transport":"%s","external_driver_pull_policy":"%s","supervisor_image":"%s","supervisor_image_id":"%s","supervisor_image_digest":"%s","supervisor_runtime_image":"%s","supervisor_base_image":"%s","supervisor_base_image_id":"%s","supervisor_base_image_digest":"%s","supervisor_base_runtime_image":"%s","supervisor_package_manifest_sha256":"%s","sandbox_image_request":"%s","sandbox_image_id":"%s","sandbox_image_digest":"%s","sandbox_runtime_image":"%s","sandbox_boundary_image":"%s","sandbox_client_image_alias":"%s","sandbox_client_image_alias_id":"%s","gateway_sha256_before_execution":"%s","cli_sha256_before_execution":"%s","conformance_sha256_before_execution":"%s","external_driver_sha256_before_execution":"%s","supervisor_sha256_before_execution":"%s","supervisor_dockerfile_sha256_before_execution":"%s","cli_trace_wrapper_sha256_before_execution":"%s","external_driver_grpc_endpoint":%s,"external_driver_host_gateway_ip":%s,"external_driver_userns":%s,"external_driver_spiffe":%s,"external_driver_proxy":%s,"external_driver_app_armor":%s,"external_driver_environment":%s}\n' \ - "${CONFIG_SCHEMA_VERSION}" \ - "${HOST_PORT}" \ - "$([ "${OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER:-0}" = "1" ] && printf true || printf false)" \ - "${driver_transport}" \ - "${EXTERNAL_DRIVER_PULL_POLICY}" \ - "${SUPERVISOR_IMAGE}" \ - "${SUPERVISOR_IMAGE_ID}" \ - "${SUPERVISOR_IMAGE_DIGEST}" \ - "${SUPERVISOR_RUNTIME_IMAGE}" \ - "${SUPERVISOR_BASE_IMAGE}" \ - "${SUPERVISOR_BASE_IMAGE_ID}" \ - "${SUPERVISOR_BASE_IMAGE_DIGEST}" \ - "${OPENSHELL_E2E_SUPERVISOR_BASE_RUNTIME_IMAGE:-${SUPERVISOR_BASE_IMAGE%@*}@${SUPERVISOR_BASE_IMAGE_DIGEST}}" \ - "${SUPERVISOR_PACKAGE_MANIFEST_SHA256}" \ - "${SANDBOX_IMAGE_REQUEST}" \ - "${SANDBOX_IMAGE_ID}" \ - "${SANDBOX_IMAGE_DIGEST}" \ - "${SANDBOX_RUNTIME_IMAGE}" \ - "${SANDBOX_BOUNDARY_IMAGE}" \ - "${SANDBOX_CLIENT_IMAGE_ALIAS}" \ - "${SANDBOX_CLIENT_IMAGE_ALIAS_ID}" \ - "${OPENSHELL_E2E_EXPECTED_GATEWAY_SHA256:-}" \ - "${OPENSHELL_E2E_EXPECTED_CLI_SHA256:-}" \ - "${OPENSHELL_E2E_EXPECTED_CONFORMANCE_SHA256:-}" \ - "${OPENSHELL_E2E_EXPECTED_EXTERNAL_DRIVER_SHA256:-}" \ - "${OPENSHELL_E2E_EXPECTED_SUPERVISOR_SHA256:-}" \ - "${OPENSHELL_E2E_EXPECTED_SUPERVISOR_DOCKERFILE_SHA256:-}" \ - "${OPENSHELL_E2E_EXPECTED_CLI_TRACE_WRAPPER_SHA256:-}" \ - "${external_driver_grpc_endpoint}" \ - "${external_driver_host_gateway_ip}" \ - "${external_driver_userns}" \ - "${external_driver_spiffe}" \ - "${external_driver_proxy}" \ - "${external_driver_app_armor}" \ - "${external_driver_environment}" \ - >"${OPENSHELL_PARITY_LAUNCH_MANIFEST_CAPTURE}" -fi - if [ "${OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER:-0}" = "1" ]; then - require_expected_sha256 "external compute driver" "${DRIVER_BIN}" \ - "${OPENSHELL_E2E_EXPECTED_EXTERNAL_DRIVER_SHA256:-}" env -i \ XDG_DATA_HOME="${DRIVER_DATA_HOME}" \ OPENSHELL_COMPUTE_DRIVER_SOCKET="${DRIVER_SOCKET}" \ @@ -847,9 +572,8 @@ if [ "${OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER:-0}" = "1" ]; then OPENSHELL_NETWORK_NAME="${PODMAN_NETWORK_NAME}" \ OPENSHELL_STOP_TIMEOUT="${PODMAN_STOP_TIMEOUT_SECS}" \ OPENSHELL_SANDBOX_RUNTIME_IMAGE="${SANDBOX_BOUNDARY_IMAGE}" \ - OPENSHELL_SUPERVISOR_IMAGE="${SUPERVISOR_RUNTIME_IMAGE}" \ + OPENSHELL_SUPERVISOR_IMAGE="${SUPERVISOR_IMAGE}" \ OPENSHELL_PODMAN_TLS_CA="${EXTERNAL_DRIVER_TLS_CA}" \ - "${external_driver_legacy_tls_env[@]}" \ OPENSHELL_ENABLE_BIND_MOUNTS="${EXTERNAL_DRIVER_ENABLE_BIND_MOUNTS}" \ "${DRIVER_BIN}" >"${DRIVER_LOG}" 2>&1 & DRIVER_PID=$! @@ -896,10 +620,8 @@ e2e_export_gateway_restart_metadata \ "${GATEWAY_LOG}" \ "${GATEWAY_PID_FILE}" -require_expected_sha256 "gateway binary" "${GATEWAY_BIN}" \ - "${OPENSHELL_E2E_EXPECTED_GATEWAY_SHA256:-}" OPENSHELL_LOCAL_TLS_DIR="${PKI_DIR}" \ -OPENSHELL_SUPERVISOR_IMAGE="${SUPERVISOR_RUNTIME_IMAGE}" \ +OPENSHELL_SUPERVISOR_IMAGE="${SUPERVISOR_IMAGE}" \ OPENSHELL_NETWORK_NAME="${PODMAN_NETWORK_NAME}" \ "${GATEWAY_BIN}" "${GATEWAY_ARGS[@]}" >"${GATEWAY_LOG}" 2>&1 & GATEWAY_PID=$! @@ -949,12 +671,6 @@ if [ "${elapsed}" -ge "${timeout}" ]; then exit 1 fi -require_expected_sha256 "OpenShell CLI" "${CLI_BIN}" \ - "${OPENSHELL_E2E_EXPECTED_CLI_SHA256:-}" -if [ -n "${OPENSHELL_E2E_EXPECTED_CONFORMANCE_SHA256:-}" ]; then - require_expected_sha256 "conformance CLI" "${OPENSHELL_CONFORMANCE_BIN}" \ - "${OPENSHELL_E2E_EXPECTED_CONFORMANCE_SHA256}" -fi # Seed the example profiles the provider tests rely on. The mTLS lanes already # have a registered gateway identity; the OIDC lanes deliberately skip # registration and have no token yet, so establish an administrator session diff --git a/python/openshell/gateway_schema_v2_capability_parity_test.py b/python/openshell/gateway_schema_v2_capability_parity_test.py deleted file mode 100644 index 9d3ff80b65..0000000000 --- a/python/openshell/gateway_schema_v2_capability_parity_test.py +++ /dev/null @@ -1,293 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Validate the schema-v2 live capability-parity manifest without a runtime.""" - -from __future__ import annotations - -import re -import tomllib -from copy import deepcopy -from pathlib import Path -from typing import Any - -import pytest - -REPO_ROOT = Path(__file__).resolve().parents[2] -MANIFEST_PATH = REPO_ROOT / "e2e/configs/gateway/schema-v2-capability-parity.toml" - -REQUIRED_MANIFEST_FIELDS = { - "manifest_version", - "baseline_ref", - "baseline_commit", - "baseline_schema_version", - "candidate_ref", - "candidate_commit", - "candidate_schema_version", - "capabilities", -} -REQUIRED_CAPABILITY_FIELDS = { - "id", - "topics", - "origin_main_access_paths", - "schema_v2_access_paths", - "behavioral_oracle", - "required_environment", - "test_lane", - "status", -} -REQUIRED_TOPICS = { - "auth_tls_jwt", - "configuration_producers", - "credentials", - "database", - "docker", - "external_drivers", - "inference", - "interceptors", - "kubernetes", - "listeners", - "middleware", - "mxc", - "observability", - "packaging_upgrades", - "podman", - "vm", -} -REQUIRED_CAPABILITY_IDS = { - "configuration-source-precedence", - "schema-version-and-strict-layout", - "gateway-identity-and-logging", - "primary-health-and-metrics-listeners", - "database-url-and-persistence-backends", - "ssh-rate-limit-and-policy-posture", - "sandbox-service-routing", - "gateway-listener-tls-and-sni", - "plaintext-listener-mode", - "guest-callback-tls-ownership", - "oidc-bearer-authentication", - "mtls-user-authentication", - "unsafe-unauthenticated-user-mode", - "gateway-minted-sandbox-jwt", - "otlp-observability", - "gateway-interceptor-registration", - "supervisor-middleware-registration", - "provider-profile-sources", - "inference-control-plane-configuration", - "credential-driver-selection-and-kek", - "credential-driver-backend-tables", - "docker-image-and-callback-configuration", - "docker-security-and-provider-configuration", - "podman-image-and-callback-configuration", - "podman-runtime-security-and-health", - "kubernetes-core-placement-and-images", - "kubernetes-workspace-isolation", - "kubernetes-supervisor-topology", - "kubernetes-egress-spiffe-and-security", - "vm-launch-and-resource-configuration", - "vm-guest-security-and-spiffe", - "mxc-windows-driver-configuration", - "external-compute-driver-socket", - "helm-configuration-producer", - "local-launch-script-producers", - "e2e-fixture-producers", - "rpm-schema-upgrade", - "homebrew-debian-and-snap-upgrades", -} -ALLOWED_LANES = { - "deterministic", - "e2e-docker", - "e2e-podman", - "e2e-kubernetes", - "e2e-vm", - "windows-mxc", - "extension-driver", - "auth-oidc", - "observability", - "packaging", -} -# This inventory plans execution. Live results belong in the execution record, -# not in this baseline manifest, so a PASS cannot be accidentally implied. -ALLOWED_STATUSES = {"not_run", "blocked", "planned"} - - -def load_manifest() -> dict[str, Any]: - with MANIFEST_PATH.open("rb") as manifest_file: - return tomllib.load(manifest_file) - - -def require_nonempty_string(value: Any, field: str, entry_id: str) -> None: - assert isinstance(value, str) and value.strip(), f"{entry_id}: {field} is required" - - -def require_string_list(value: Any, field: str, entry_id: str) -> None: - assert isinstance(value, list) and value, f"{entry_id}: {field} must be non-empty" - assert all(isinstance(item, str) and item.strip() for item in value), ( - f"{entry_id}: {field} must contain only non-empty strings" - ) - assert len(value) == len(set(value)), f"{entry_id}: {field} contains duplicates" - - -def validate_manifest(manifest: dict[str, Any]) -> None: - assert set(manifest) == REQUIRED_MANIFEST_FIELDS, ( - "unexpected or missing manifest metadata" - ) - assert manifest["manifest_version"] == 1 - assert manifest["baseline_ref"] == "origin/main" - assert manifest["baseline_commit"] == "74960ebfaeec4673885089ed995fad902459749f" - assert manifest["baseline_schema_version"] == 1 - assert manifest["candidate_ref"] == "HEAD" - assert manifest["candidate_commit"] == "8c868e430e9cd3284d7e274628419ab484ebcee0" - assert manifest["candidate_schema_version"] == 2 - - capabilities = manifest["capabilities"] - assert isinstance(capabilities, list) and capabilities, ( - "capabilities must be non-empty" - ) - ids: list[str] = [] - topics: set[str] = set() - for capability in capabilities: - assert isinstance(capability, dict), "each capability must be a TOML table" - assert set(capability) == REQUIRED_CAPABILITY_FIELDS, ( - "capability has unexpected or missing metadata" - ) - entry_id = capability["id"] - require_nonempty_string(entry_id, "id", "capability") - ids.append(entry_id) - require_string_list(capability["topics"], "topics", entry_id) - topics.update(capability["topics"]) - require_string_list( - capability["origin_main_access_paths"], - "origin_main_access_paths", - entry_id, - ) - require_string_list( - capability["schema_v2_access_paths"], - "schema_v2_access_paths", - entry_id, - ) - require_nonempty_string( - capability["behavioral_oracle"], "behavioral_oracle", entry_id - ) - require_nonempty_string( - capability["required_environment"], "required_environment", entry_id - ) - assert capability["test_lane"] in ALLOWED_LANES, ( - f"{entry_id}: unknown test lane {capability['test_lane']!r}" - ) - assert capability["status"] in ALLOWED_STATUSES, ( - f"{entry_id}: live PASS results are not valid in this planning manifest" - ) - - assert len(ids) == len(set(ids)), "capability IDs must be unique" - assert set(ids) == REQUIRED_CAPABILITY_IDS, ( - "capability inventory is incomplete or stale" - ) - assert topics == REQUIRED_TOPICS, ( - "topic inventory is incomplete or contains an unknown topic" - ) - - -def capability_by_id(manifest: dict[str, Any], capability_id: str) -> dict[str, Any]: - return next( - capability - for capability in manifest["capabilities"] - if capability["id"] == capability_id - ) - - -def test_schema_v2_capability_parity_manifest_is_well_formed() -> None: - validate_manifest(load_manifest()) - - -def test_frozen_comparison_commits_are_full_git_object_ids() -> None: - manifest = load_manifest() - - for field in ("baseline_commit", "candidate_commit"): - commit = manifest[field] - assert len(commit) == 40 - assert all(character in "0123456789abcdef" for character in commit) - - -def test_vm_gateway_inventory_excludes_standalone_driver_only_fields() -> None: - capability = capability_by_id( - load_manifest(), "vm-launch-and-resource-configuration" - ) - gateway_paths = " ".join( - capability["origin_main_access_paths"] + capability["schema_v2_access_paths"] - ) - - gateway_fields = set(re.findall(r"[a-z][a-z0-9_]*", gateway_paths)) - for standalone_field in ("launcher_bin", "log_level", "gpu_enabled", "gpu_mem_mib"): - assert standalone_field not in gateway_fields - assert "driver_dir" in gateway_fields - - -def test_new_docker_capabilities_are_not_attributed_to_origin_main() -> None: - capability = capability_by_id( - load_manifest(), "docker-security-and-provider-configuration" - ) - origin_paths = " ".join(capability["origin_main_access_paths"]) - candidate_paths = " ".join(capability["schema_v2_access_paths"]) - - assert "no origin/main Docker equivalent" in origin_paths - for added_field in ( - "https_proxy", - "provider_spiffe_workload_api_socket", - "app_armor_profile", - ): - assert added_field not in origin_paths - assert added_field in candidate_paths - - -@pytest.mark.parametrize("field", sorted(REQUIRED_MANIFEST_FIELDS - {"capabilities"})) -def test_manifest_rejects_missing_header_metadata(field: str) -> None: - manifest = deepcopy(load_manifest()) - del manifest[field] - - with pytest.raises(AssertionError, match="missing manifest metadata"): - validate_manifest(manifest) - - -@pytest.mark.parametrize("field", sorted(REQUIRED_CAPABILITY_FIELDS - {"id"})) -def test_manifest_rejects_missing_capability_metadata(field: str) -> None: - manifest = deepcopy(load_manifest()) - del manifest["capabilities"][0][field] - - with pytest.raises(AssertionError, match=r"metadata|required|must be"): - validate_manifest(manifest) - - -@pytest.mark.parametrize( - ("field", "value", "match"), - [ - ("topics", [], "must be non-empty"), - ("behavioral_oracle", "", "is required"), - ("required_environment", "", "is required"), - ("test_lane", "not-a-lane", "unknown test lane"), - ], -) -def test_manifest_rejects_malformed_capability_metadata( - field: str, value: Any, match: str -) -> None: - manifest = deepcopy(load_manifest()) - manifest["capabilities"][0][field] = value - - with pytest.raises(AssertionError, match=match): - validate_manifest(manifest) - - -def test_manifest_rejects_duplicate_capability_id() -> None: - manifest = deepcopy(load_manifest()) - manifest["capabilities"][1]["id"] = manifest["capabilities"][0]["id"] - - with pytest.raises(AssertionError, match=r"unique|incomplete"): - validate_manifest(manifest) - - -def test_manifest_does_not_claim_live_pass_results() -> None: - manifest = deepcopy(load_manifest()) - manifest["capabilities"][0]["status"] = "pass" - - with pytest.raises(AssertionError, match="live PASS"): - validate_manifest(manifest) diff --git a/python/openshell/gateway_schema_v2_compute_boundary_verifier_test.py b/python/openshell/gateway_schema_v2_compute_boundary_verifier_test.py deleted file mode 100644 index 5e93d7cbe2..0000000000 --- a/python/openshell/gateway_schema_v2_compute_boundary_verifier_test.py +++ /dev/null @@ -1,489 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Exercise the retained-artifact verifier used by schema-v2 Step 10.""" - -from __future__ import annotations - -import importlib.util -import json -from pathlib import Path -from typing import TYPE_CHECKING - -import pytest - -if TYPE_CHECKING: - from types import ModuleType - -REPO_ROOT = Path(__file__).resolve().parents[2] -VERIFIER_PATH = REPO_ROOT / "e2e/parity/verify-results.py" -BASELINE_SHA = "1" * 40 -CANDIDATE_SHA = "2" * 40 -IMAGE_ID = "3" * 64 -IMAGE_DIGEST = f"sha256:{'4' * 64}" -RUNTIME_IMAGE = f"localhost/openshell/supervisor@{IMAGE_DIGEST}" -BOUNDARY_IMAGE = f"localhost/openshell/sandbox@{IMAGE_DIGEST}" -BASE_RUNTIME_IMAGE = f"docker.io/library/alpine@{IMAGE_DIGEST}" - - -def load_verifier() -> ModuleType: - spec = importlib.util.spec_from_file_location("parity_verifier", VERIFIER_PATH) - assert spec is not None and spec.loader is not None - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -def write_json(path: Path, value: object) -> None: - path.write_text(json.dumps(value) + "\n", encoding="utf-8") - - -def create_variant( - verifier: ModuleType, - results_dir: Path, - variant: str, - source_sha: str, - schema_version: int, -) -> None: - artifact_dir = results_dir / "artifacts" / variant - artifact_dir.mkdir(parents=True) - artifacts = { - "gateway": f"{variant}-gateway", - "cli": f"{variant}-cli", - "conformance": f"{variant}-conformance", - "supervisor": f"{variant}-supervisor", - "supervisor.Dockerfile": f"{variant}-dockerfile", - "cli-trace-wrapper": "shared-cli-trace-wrapper", - "external-driver": f"{variant}-external-driver", - "supervisor.packages.txt": "fixture-package-1.0-r0\n", - } - for filename, content in artifacts.items(): - (artifact_dir / filename).write_text(content, encoding="utf-8") - - write_json( - results_dir / f"{variant}.json", - { - "variant": variant, - "source_sha": source_sha, - "schema_version": schema_version, - "driver": "podman", - "scenario": "external-driver", - "gateway_profile": "driver-free", - "gateway_cargo_features": "--no-default-features --features telemetry", - "gateway_origin": "built_by_harness", - "cli_origin": "built_by_harness", - "conformance_origin": "built_by_harness", - "external_driver_origin": "built_by_harness", - "supervisor_origin": "built_by_harness", - "gateway_sha256": verifier.sha256(artifact_dir / "gateway"), - "cli_sha256": verifier.sha256(artifact_dir / "cli"), - "conformance_sha256": verifier.sha256(artifact_dir / "conformance"), - "supervisor_sha256": verifier.sha256(artifact_dir / "supervisor"), - "supervisor_dockerfile_sha256": verifier.sha256( - artifact_dir / "supervisor.Dockerfile" - ), - "cli_trace_wrapper_sha256": verifier.sha256( - artifact_dir / "cli-trace-wrapper" - ), - "external_driver_sha256": verifier.sha256(artifact_dir / "external-driver"), - "success": True, - }, - ) - selector = ( - 'compute_drivers = ["podman"]' - if schema_version == 1 - else 'compute_driver = "podman"' - ) - (results_dir / f"{variant}.gateway.toml").write_text( - f"""[openshell] -version = {schema_version} -[openshell.gateway] -{selector} -[openshell.drivers.podman] -socket_path = "/tmp/{variant}.sock" -""", - encoding="utf-8", - ) - policy = "missing" if schema_version == 1 else "if_not_present" - package_hash = verifier.sha256(artifact_dir / "supervisor.packages.txt") - result = json.loads((results_dir / f"{variant}.json").read_text(encoding="utf-8")) - legacy_tls_environment = {} - if schema_version == 1: - legacy_tls_environment = { - "OPENSHELL_PODMAN_TLS_CERT": { - "path": f"/tmp/{variant}-pki/tls.crt", - "sha256": "9" * 64, - }, - "OPENSHELL_PODMAN_TLS_KEY": { - "path": f"/tmp/{variant}-pki/tls.key", - "sha256": "a" * 64, - }, - } - write_json( - results_dir / f"{variant}.launch.json", - { - "schema_version": schema_version, - "gateway_port": 18181, - "external_compute_driver": True, - "compute_driver_transport": "remote_uds", - "external_driver_pull_policy": policy, - "supervisor_image_id": IMAGE_ID, - "supervisor_image_digest": IMAGE_DIGEST, - "supervisor_runtime_image": RUNTIME_IMAGE, - "supervisor_base_image": "alpine:fixture", - "supervisor_base_image_id": IMAGE_ID, - "supervisor_base_image_digest": IMAGE_DIGEST, - "supervisor_base_runtime_image": BASE_RUNTIME_IMAGE, - "supervisor_package_manifest_sha256": package_hash, - "sandbox_image_request": "example.invalid/sandbox@" + IMAGE_DIGEST, - "sandbox_image_id": IMAGE_ID, - "sandbox_image_digest": IMAGE_DIGEST, - "sandbox_runtime_image": "example.invalid/sandbox@" + IMAGE_DIGEST, - "sandbox_boundary_image": BOUNDARY_IMAGE, - "sandbox_client_image_alias": "example.invalid/sandbox:latest", - "sandbox_client_image_alias_id": IMAGE_ID, - "gateway_sha256_before_execution": result["gateway_sha256"], - "cli_sha256_before_execution": result["cli_sha256"], - "conformance_sha256_before_execution": result["conformance_sha256"], - "external_driver_sha256_before_execution": result["external_driver_sha256"], - "supervisor_sha256_before_execution": result["supervisor_sha256"], - "supervisor_dockerfile_sha256_before_execution": result[ - "supervisor_dockerfile_sha256" - ], - "cli_trace_wrapper_sha256_before_execution": result[ - "cli_trace_wrapper_sha256" - ], - "external_driver_grpc_endpoint": "https://127.0.0.1:18181", - "external_driver_host_gateway_ip": "host-gateway", - "external_driver_userns": None, - "external_driver_spiffe": False, - "external_driver_proxy": False, - "external_driver_app_armor": False, - "external_driver_environment": { - "XDG_DATA_HOME": f"/tmp/{variant}-driver-data", - "OPENSHELL_COMPUTE_DRIVER_SOCKET": f"/tmp/{variant}.sock", - "OPENSHELL_PODMAN_SOCKET": f"/tmp/{variant}-podman.sock", - "OPENSHELL_SANDBOX_IMAGE": "example.invalid/sandbox@" + IMAGE_DIGEST, - "OPENSHELL_SANDBOX_IMAGE_PULL_POLICY": policy, - "OPENSHELL_HEALTH_CHECK_INTERVAL_SECS": 10, - "OPENSHELL_GRPC_ENDPOINT": "https://127.0.0.1:18181", - "OPENSHELL_GATEWAY_PORT": 18181, - "OPENSHELL_NETWORK_NAME": f"{variant}-network", - "OPENSHELL_STOP_TIMEOUT": 15, - "OPENSHELL_SANDBOX_RUNTIME_IMAGE": BOUNDARY_IMAGE, - "OPENSHELL_SUPERVISOR_IMAGE": RUNTIME_IMAGE, - "OPENSHELL_PODMAN_TLS_CA": { - "path": f"/tmp/{variant}-pki/ca.crt", - "sha256": "8" * 64, - }, - **legacy_tls_environment, - "OPENSHELL_ENABLE_BIND_MOUNTS": True, - }, - }, - ) - lifecycle = "\n".join( - f"[run fixture{marker} in 1ms: exit 0" for marker in verifier.ORACLE_MARKERS - ) - (results_dir / f"{variant}.log").write_text( - f"CLI conformance run ID: fixture\n" - f"gateway preflight connected: gateway=fixture, authentication=authenticated\n" - f"{lifecycle}\n{RUNTIME_IMAGE} {BOUNDARY_IMAGE} {BASE_RUNTIME_IMAGE} example.invalid/sandbox@{IMAGE_DIGEST} example.invalid/sandbox:latest " - f'{IMAGE_ID} {IMAGE_DIGEST} {package_hash}\n"passed": true\n', - encoding="utf-8", - ) - (results_dir / f"{variant}.exec.stdout").write_bytes( - b"openshell-conformance-fixture\n" - ) - write_json( - results_dir / f"{variant}.conformance.json", - { - "scenarios": [{"name": "smoke", "passed": True, "diagnostic": None}], - "passed": True, - }, - ) - (results_dir / f"{variant}.driver.log").write_text( - "external driver fixture started\n", encoding="utf-8" - ) - - -def create_external_bundle(verifier: ModuleType, results_dir: Path) -> None: - create_variant(verifier, results_dir, "baseline", BASELINE_SHA, 1) - create_variant(verifier, results_dir, "candidate", CANDIDATE_SHA, 2) - write_json( - results_dir / "comparison.json", - { - "scenario": "external-driver", - "baseline_success": True, - "candidate_success": True, - "parity": True, - "accepted": True, - "classification": "pass", - }, - ) - - -def test_single_scenario_verifier_accepts_bound_supplied_artifacts( - tmp_path: Path, -) -> None: - verifier = load_verifier() - create_external_bundle(verifier, tmp_path) - (tmp_path / "comparison.json").unlink() - for variant in ("baseline", "candidate"): - result_path = tmp_path / f"{variant}.json" - result = json.loads(result_path.read_text(encoding="utf-8")) - for field in ( - "gateway_origin", - "cli_origin", - "conformance_origin", - "external_driver_origin", - "supervisor_origin", - ): - result[field] = "supplied_override" - write_json(result_path, result) - - report = verifier.verify_topology( - tmp_path, - BASELINE_SHA, - CANDIDATE_SHA, - "external-driver", - verify_comparison=False, - require_built_artifacts=False, - ) - - assert report["comparison_sha256"] is None - assert report["baseline"]["raw_output_verified"] is True - assert report["candidate"]["artifacts_verified"] is True - assert report["candidate"]["conformance_report_verified"] is True - - -def test_single_scenario_verifier_rejects_failed_conformance_report( - tmp_path: Path, -) -> None: - verifier = load_verifier() - create_external_bundle(verifier, tmp_path) - report_path = tmp_path / "candidate.conformance.json" - report = json.loads(report_path.read_text(encoding="utf-8")) - report["scenarios"][0]["passed"] = False - report["scenarios"][0]["diagnostic"] = "fixture failure" - report["passed"] = False - write_json(report_path, report) - - with pytest.raises(ValueError, match="conformance report did not pass"): - verifier.verify_topology( - tmp_path, - BASELINE_SHA, - CANDIDATE_SHA, - "external-driver", - verify_comparison=False, - require_built_artifacts=False, - ) - - -def test_verifier_recomputes_retained_artifact_hashes(tmp_path: Path) -> None: - verifier = load_verifier() - create_external_bundle(verifier, tmp_path) - - report = verifier.verify_topology( - tmp_path, BASELINE_SHA, CANDIDATE_SHA, "external-driver" - ) - assert report["baseline"]["artifacts_verified"] is True - assert report["candidate"]["raw_output_verified"] is True - - (tmp_path / "artifacts/candidate/gateway").write_text( - "mutated after execution", encoding="utf-8" - ) - with pytest.raises(ValueError, match="retained artifact hash mismatch"): - verifier.verify_topology( - tmp_path, BASELINE_SHA, CANDIDATE_SHA, "external-driver" - ) - - -def test_verifier_rejects_mutable_sandbox_request(tmp_path: Path) -> None: - verifier = load_verifier() - create_external_bundle(verifier, tmp_path) - launch_path = tmp_path / "candidate.launch.json" - launch = json.loads(launch_path.read_text(encoding="utf-8")) - launch["sandbox_image_request"] = "example.invalid/sandbox:latest" - write_json(launch_path, launch) - - with pytest.raises( - ValueError, match="request was not the resolved digest reference" - ): - verifier.verify_topology( - tmp_path, BASELINE_SHA, CANDIDATE_SHA, "external-driver" - ) - - -def test_verifier_rejects_different_sandbox_artifacts(tmp_path: Path) -> None: - verifier = load_verifier() - create_external_bundle(verifier, tmp_path) - launch_path = tmp_path / "candidate.launch.json" - launch = json.loads(launch_path.read_text(encoding="utf-8")) - other_id = "5" * 64 - other_digest = f"sha256:{'6' * 64}" - other_runtime = f"example.invalid/sandbox@{other_digest}" - launch.update( - { - "sandbox_image_request": other_runtime, - "sandbox_image_id": other_id, - "sandbox_image_digest": other_digest, - "sandbox_runtime_image": other_runtime, - "sandbox_client_image_alias_id": other_id, - } - ) - launch["external_driver_environment"]["OPENSHELL_SANDBOX_IMAGE"] = other_runtime - write_json(launch_path, launch) - with (tmp_path / "candidate.log").open("a", encoding="utf-8") as log: - log.write(f"{other_id} {other_digest} {other_runtime}\n") - - with pytest.raises(ValueError, match="sandbox image ID differ"): - verifier.verify_topology( - tmp_path, BASELINE_SHA, CANDIDATE_SHA, "external-driver" - ) - - -def test_verifier_rejects_different_supervisor_packages(tmp_path: Path) -> None: - verifier = load_verifier() - create_external_bundle(verifier, tmp_path) - package_path = tmp_path / "artifacts/candidate/supervisor.packages.txt" - package_path.write_text("fixture-package-2.0-r0\n", encoding="utf-8") - package_hash = verifier.sha256(package_path) - launch_path = tmp_path / "candidate.launch.json" - launch = json.loads(launch_path.read_text(encoding="utf-8")) - launch["supervisor_package_manifest_sha256"] = package_hash - write_json(launch_path, launch) - with (tmp_path / "candidate.log").open("a", encoding="utf-8") as log: - log.write(f"{package_hash}\n") - - with pytest.raises(ValueError, match="supervisor package manifest differ"): - verifier.verify_topology( - tmp_path, BASELINE_SHA, CANDIDATE_SHA, "external-driver" - ) - - -def test_verifier_rejects_unattested_external_driver_input(tmp_path: Path) -> None: - verifier = load_verifier() - create_external_bundle(verifier, tmp_path) - launch_path = tmp_path / "candidate.launch.json" - launch = json.loads(launch_path.read_text(encoding="utf-8")) - del launch["external_driver_environment"]["OPENSHELL_STOP_TIMEOUT"] - write_json(launch_path, launch) - - with pytest.raises(ValueError, match="allowlisted environment is incomplete"): - verifier.verify_topology( - tmp_path, BASELINE_SHA, CANDIDATE_SHA, "external-driver" - ) - - -def test_verifier_binds_gateway_and_driver_uds(tmp_path: Path) -> None: - verifier = load_verifier() - create_external_bundle(verifier, tmp_path) - launch_path = tmp_path / "candidate.launch.json" - launch = json.loads(launch_path.read_text(encoding="utf-8")) - launch["external_driver_environment"]["OPENSHELL_COMPUTE_DRIVER_SOCKET"] = ( - "/tmp/different.sock" - ) - write_json(launch_path, launch) - - with pytest.raises(ValueError, match="driver socket differs from gateway TOML"): - verifier.verify_topology( - tmp_path, BASELINE_SHA, CANDIDATE_SHA, "external-driver" - ) - - -def test_verifier_rejects_reused_external_uds(tmp_path: Path) -> None: - verifier = load_verifier() - create_external_bundle(verifier, tmp_path) - launch_path = tmp_path / "candidate.launch.json" - launch = json.loads(launch_path.read_text(encoding="utf-8")) - launch["external_driver_environment"]["OPENSHELL_COMPUTE_DRIVER_SOCKET"] = ( - "/tmp/baseline.sock" - ) - write_json(launch_path, launch) - config_path = tmp_path / "candidate.gateway.toml" - config_path.write_text( - config_path.read_text(encoding="utf-8").replace( - "/tmp/candidate.sock", "/tmp/baseline.sock" - ), - encoding="utf-8", - ) - - with pytest.raises(ValueError, match="reuse the same external compute-driver UDS"): - verifier.verify_topology( - tmp_path, BASELINE_SHA, CANDIDATE_SHA, "external-driver" - ) - - -def test_verifier_requires_exact_exec_stdout(tmp_path: Path) -> None: - verifier = load_verifier() - create_external_bundle(verifier, tmp_path) - (tmp_path / "candidate.exec.stdout").write_text("wrong marker\n", encoding="utf-8") - - with pytest.raises(ValueError, match="stdout is not the exact marker"): - verifier.verify_topology( - tmp_path, BASELINE_SHA, CANDIDATE_SHA, "external-driver" - ) - - -def test_verifier_requires_authenticated_preflight(tmp_path: Path) -> None: - verifier = load_verifier() - create_external_bundle(verifier, tmp_path) - log_path = tmp_path / "candidate.log" - log_path.write_text( - log_path.read_text(encoding="utf-8").replace( - "authentication=authenticated", "authentication=unauthenticated" - ), - encoding="utf-8", - ) - - with pytest.raises(ValueError, match="authenticated gateway preflight is missing"): - verifier.verify_topology( - tmp_path, BASELINE_SHA, CANDIDATE_SHA, "external-driver" - ) - - -def test_verifier_rejects_cross_topology_sandbox_drift() -> None: - verifier = load_verifier() - launch = { - "sandbox_image_id": IMAGE_ID, - "sandbox_image_digest": IMAGE_DIGEST, - "sandbox_runtime_image": "example.invalid/sandbox@" + IMAGE_DIGEST, - "sandbox_client_image_alias": "example.invalid/sandbox:latest", - "sandbox_client_image_alias_id": IMAGE_ID, - "supervisor_base_image": "alpine:fixture", - "supervisor_base_image_id": IMAGE_ID, - "supervisor_base_image_digest": IMAGE_DIGEST, - "supervisor_base_runtime_image": BASE_RUNTIME_IMAGE, - "supervisor_package_manifest_sha256": "7" * 64, - } - shared_artifacts = { - "cli": "b" * 64, - "conformance": "c" * 64, - "supervisor": "d" * 64, - "supervisor.Dockerfile": "e" * 64, - "cli-trace-wrapper": "f" * 64, - } - in_tree = { - "baseline": { - "launch_attestation": dict(launch), - "artifact_sha256": dict(shared_artifacts), - }, - "candidate": { - "launch_attestation": dict(launch), - "artifact_sha256": dict(shared_artifacts), - }, - } - external = { - "baseline": { - "launch_attestation": dict(launch), - "artifact_sha256": dict(shared_artifacts), - }, - "candidate": { - "launch_attestation": dict(launch), - "artifact_sha256": dict(shared_artifacts), - }, - } - external["candidate"]["launch_attestation"]["sandbox_image_id"] = "8" * 64 - - with pytest.raises(ValueError, match="four runs use different sandbox artifact"): - verifier.verify_four_run_provenance(in_tree, external) diff --git a/python/openshell/gateway_schema_v2_cross_cutting_dispositions_test.py b/python/openshell/gateway_schema_v2_cross_cutting_dispositions_test.py deleted file mode 100644 index dc83417e85..0000000000 --- a/python/openshell/gateway_schema_v2_cross_cutting_dispositions_test.py +++ /dev/null @@ -1,173 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Validate schema-v2 Step 11 cross-cutting capability dispositions.""" - -from __future__ import annotations - -import hashlib -import re -import tomllib -from pathlib import Path -from typing import Any - -REPO_ROOT = Path(__file__).resolve().parents[2] -DISPOSITIONS_PATH = ( - REPO_ROOT / "e2e/configs/gateway/schema-v2-cross-cutting-dispositions.toml" -) -CAPABILITY_PATH = REPO_ROOT / "e2e/configs/gateway/schema-v2-capability-parity.toml" -INTENTIONAL_CHANGES_PATH = ( - REPO_ROOT / "e2e/configs/gateway/schema-v2-intentional-changes.toml" -) - -STEP_11_CAPABILITY_IDS = { - "credential-driver-backend-tables", - "credential-driver-selection-and-kek", - "gateway-interceptor-registration", - "gateway-minted-sandbox-jwt", - "inference-control-plane-configuration", - "mtls-user-authentication", - "oidc-bearer-authentication", - "otlp-observability", - "provider-profile-sources", - "supervisor-middleware-registration", - "unsafe-unauthenticated-user-mode", -} -EXPECTED_SUITES = { - "server-lib": "cargo test -q -p openshell-server --lib", - "gateway-interceptors": "cargo test -q -p openshell-gateway-interceptors", - "supervisor-middleware": "cargo test -q -p openshell-supervisor-middleware", - "otel-test-support": "cargo test -q -p openshell-otel-test-support", - "multiplex-tls-integration": ( - "cargo test -q -p openshell-server --test multiplex_tls_integration" - ), - "edge-tunnel-auth": "cargo test -q -p openshell-server --test edge_tunnel_auth", -} -EXPECTED_CANDIDATE_COMMIT = "363d8540830b2ea294d43198daa2b7a283a2face" -EXPECTED_INTENTIONAL_CHANGES = { - "gateway-jwt-zero-sentinel-removed": "gateway-minted-sandbox-jwt", - "middleware-payload-name-normalized": "supervisor-middleware-registration", -} - - -def load_toml(path: Path) -> dict[str, Any]: - with path.open("rb") as toml_file: - return tomllib.load(toml_file) - - -def assert_full_sha(value: object, field: str) -> str: - assert isinstance(value, str), f"{field} must be a string" - assert re.fullmatch(r"[0-9a-f]{40}", value), f"{field} must be a full SHA" - return value - - -def test_step_11_dispositions_cover_exact_cross_cutting_capabilities() -> None: - manifest = load_toml(DISPOSITIONS_PATH) - - assert set(manifest) == { - "manifest_version", - "baseline_commit", - "candidate_commit", - "overall_status", - "deterministic_preflight", - "capabilities", - } - assert manifest["manifest_version"] == 1 - assert manifest["overall_status"] == "platform_blocked" - assert ( - assert_full_sha(manifest["baseline_commit"], "baseline_commit") - == load_toml(CAPABILITY_PATH)["baseline_commit"] - ) - assert ( - assert_full_sha(manifest["candidate_commit"], "candidate_commit") - == EXPECTED_CANDIDATE_COMMIT - ) - - capabilities = manifest["capabilities"] - assert {entry["id"] for entry in capabilities} == STEP_11_CAPABILITY_IDS - assert len(capabilities) == len(STEP_11_CAPABILITY_IDS) - for entry in capabilities: - assert set(entry) == {"id", "status", "owner", "lane", "blocker"} - assert entry["status"] == "platform_blocked" - assert all(entry[field].strip() for field in ("owner", "lane")) - assert len(entry["blocker"]) >= 160 - - -def test_step_11_capability_ids_exist_in_inventory() -> None: - inventory_ids = { - entry["id"] for entry in load_toml(CAPABILITY_PATH)["capabilities"] - } - assert inventory_ids >= STEP_11_CAPABILITY_IDS - - -def test_deterministic_preflight_is_not_reported_as_live_parity() -> None: - manifest = load_toml(DISPOSITIONS_PATH) - preflight = manifest["deterministic_preflight"] - - assert set(preflight) == { - "status", - "baseline_attestation", - "baseline_attestation_sha256", - "candidate_attestation", - "candidate_attestation_sha256", - "commands", - "evidence", - } - assert preflight["status"] == "pass" - assert set(preflight["commands"]) == set(EXPECTED_SUITES.values()) - assert len(preflight["commands"]) == len(EXPECTED_SUITES) - assert len(preflight["evidence"]) >= 4 - assert all(item.strip() for item in preflight["evidence"]) - - assert manifest["overall_status"] == "platform_blocked" - assert all( - entry["status"] == "platform_blocked" for entry in manifest["capabilities"] - ) - - attestations = { - "baseline": manifest["baseline_commit"], - "candidate": manifest["candidate_commit"], - } - for variant, source_commit in attestations.items(): - path_field = f"{variant}_attestation" - digest_field = f"{variant}_attestation_sha256" - path = REPO_ROOT / preflight[path_field] - assert path.is_file(), f"missing tracked Step 11 attestation: {path}" - assert re.fullmatch(r"[0-9a-f]{64}", preflight[digest_field]) - assert hashlib.sha256(path.read_bytes()).hexdigest() == preflight[digest_field] - - attestation = path.read_text(encoding="utf-8") - assert "schema_v2_step11_deterministic_attestation_version=1\n" in attestation - assert f"variant={variant}\n" in attestation - assert f"source_commit={source_commit}\n" in attestation - assert "source_tree_clean=true\n" in attestation - assert "cargo_target_scope=checkout-local\n" in attestation - assert "rustc_wrapper=disabled\n" in attestation - assert attestation.endswith("\nattestation_complete=true\n") - suite_sections = {} - for section in attestation.split("\n=== suite:")[1:]: - suite, separator, body = section.partition(" ===\n") - assert separator - assert suite not in suite_sections - suite_sections[suite] = body - assert set(suite_sections) == set(EXPECTED_SUITES) - for suite, command in EXPECTED_SUITES.items(): - section = suite_sections[suite] - assert section.startswith(f"command={command} \n--- output ---\n") - assert section.count("--- exit_status:0 ---") == 1 - assert "--- exit_status:" not in section.replace( - "--- exit_status:0 ---", "" - ) - - -def test_step_11_representation_changes_remain_in_intentional_change_ledger() -> None: - changes = { - entry["id"]: entry - for entry in load_toml(INTENTIONAL_CHANGES_PATH)["intentional_changes"] - } - - for change_id, capability_id in EXPECTED_INTENTIONAL_CHANGES.items(): - assert change_id in changes - change = changes[change_id] - assert change["parity_disposition"] == "intentional_change" - assert capability_id in change["validation_capability_ids"] diff --git a/python/openshell/gateway_schema_v2_intentional_changes_test.py b/python/openshell/gateway_schema_v2_intentional_changes_test.py deleted file mode 100644 index 84e8f6968b..0000000000 --- a/python/openshell/gateway_schema_v2_intentional_changes_test.py +++ /dev/null @@ -1,158 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Validate the explicit schema-v2 intentional-change ledger.""" - -from __future__ import annotations - -import tomllib -from pathlib import Path -from typing import Any - -REPO_ROOT = Path(__file__).resolve().parents[2] -LEDGER_PATH = REPO_ROOT / "e2e/configs/gateway/schema-v2-intentional-changes.toml" -CAPABILITY_PATH = REPO_ROOT / "e2e/configs/gateway/schema-v2-capability-parity.toml" - -REQUIRED_HEADER_FIELDS = { - "ledger_version", - "issue", - "baseline_commit", - "candidate_start_commit", - "intentional_changes", -} -REQUIRED_CHANGE_FIELDS = { - "id", - "category", - "origin_main_contract", - "schema_v2_contract", - "migration", - "rationale", - "parity_disposition", - "validation_capability_ids", -} -REQUIRED_CHANGE_IDS = { - "canonical-image-pull-policy", - "docker-sandbox-label-rename", - "driver-table-exclusive-ownership", - "gateway-jwt-zero-sentinel-removed", - "guest-tls-centralized", - "kubernetes-fields-relocated", - "middleware-payload-name-normalized", - "package-default-only-auto-migration", - "podman-health-zero-sentinel-removed", - "podman-pid-limit-restored", - "podman-ssh-socket-rename", - "sandbox-pid-zero-sentinel-removed", - "schema-version-cutover", - "singular-compute-driver-selector", - "vm-grpc-endpoint-rename", - "vm-sandbox-identity-selection", -} -ALLOWED_CATEGORIES = { - "bug_fix", - "cardinality", - "default_behavior", - "migration_policy", - "ownership", - "relocation", - "rename", - "rename_with_alias", - "schema_cutover", - "sentinel_removal", - "type_normalization", -} - - -def load_toml(path: Path) -> dict[str, Any]: - with path.open("rb") as toml_file: - return tomllib.load(toml_file) - - -def require_nonempty_string(value: Any, field: str, change_id: str) -> None: - assert isinstance(value, str) and value.strip(), f"{change_id}: {field} is required" - - -def test_intentional_change_ledger_is_complete_and_well_formed() -> None: - ledger = load_toml(LEDGER_PATH) - - assert set(ledger) == REQUIRED_HEADER_FIELDS - assert ledger["ledger_version"] == 1 - assert ledger["issue"] == 2792 - assert ledger["baseline_commit"] == "74960ebfaeec4673885089ed995fad902459749f" - assert ledger["candidate_start_commit"] == ( - "8c868e430e9cd3284d7e274628419ab484ebcee0" - ) - - changes = ledger["intentional_changes"] - assert isinstance(changes, list) and changes - ids: list[str] = [] - for change in changes: - assert set(change) == REQUIRED_CHANGE_FIELDS - change_id = change["id"] - require_nonempty_string(change_id, "id", "intentional change") - ids.append(change_id) - assert change["category"] in ALLOWED_CATEGORIES - assert change["parity_disposition"] == "intentional_change" - for field in ( - "origin_main_contract", - "schema_v2_contract", - "migration", - "rationale", - ): - require_nonempty_string(change[field], field, change_id) - capability_ids = change["validation_capability_ids"] - assert isinstance(capability_ids, list) and capability_ids - assert len(capability_ids) == len(set(capability_ids)) - - assert len(ids) == len(set(ids)) - assert set(ids) == REQUIRED_CHANGE_IDS - - -def test_every_intentional_change_links_to_known_capabilities() -> None: - ledger = load_toml(LEDGER_PATH) - capabilities = load_toml(CAPABILITY_PATH)["capabilities"] - known_capability_ids = {capability["id"] for capability in capabilities} - - for change in ledger["intentional_changes"]: - assert set(change["validation_capability_ids"]) <= known_capability_ids, ( - f"{change['id']}: unknown validation capability" - ) - - -def test_ledger_does_not_hide_known_unresolved_parity_gaps() -> None: - ledger = load_toml(LEDGER_PATH) - change_ids = {change["id"] for change in ledger["intentional_changes"]} - - assert "legacy-environment-selector-upgrade" not in change_ids - assert "tls-require-client-auth-ignored" not in change_ids - assert "debian-snap-v1-upgrade" not in change_ids - - -def test_singular_selector_ledger_preserves_auto_detection() -> None: - ledger = load_toml(LEDGER_PATH) - selector = next( - change - for change in ledger["intentional_changes"] - if change["id"] == "singular-compute-driver-selector" - ) - - assert "omission retains built-in auto-detection" in selector["schema_v2_contract"] - assert "unsupported multi-driver operation" in selector["rationale"] - - -def test_operator_edited_package_configuration_is_never_auto_rewritten() -> None: - ledger = load_toml(LEDGER_PATH) - package_policy = next( - change - for change in ledger["intentional_changes"] - if change["id"] == "package-default-only-auto-migration" - ) - - contract = package_policy["schema_v2_contract"] - assert "byte-identical package defaults" in contract - assert "Debian and Snap preserve every legacy file" in contract - assert "fail closed through read-only package preflight" in contract - assert "manual-migration guidance" in contract - assert "no safe provenance marker" in contract - assert "preserve every edited file" in package_policy["migration"] - assert "manual schema-v2 conversion" in package_policy["migration"] diff --git a/python/openshell/gateway_schema_v2_kubernetes_option_dispositions_test.py b/python/openshell/gateway_schema_v2_kubernetes_option_dispositions_test.py deleted file mode 100644 index deb413b0da..0000000000 --- a/python/openshell/gateway_schema_v2_kubernetes_option_dispositions_test.py +++ /dev/null @@ -1,139 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Validate field-level Step 8 Kubernetes parity dispositions.""" - -import json -import re -import tomllib -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[2] -LEDGER = ( - ROOT - / "e2e" - / "configs" - / "gateway" - / "schema-v2-kubernetes-option-dispositions.toml" -) -CAPABILITIES = ROOT / "e2e" / "configs" / "gateway" / "schema-v2-capability-parity.toml" -LIVE_RESULTS = ROOT / "e2e" / "configs" / "gateway" / "schema-v2-live-results.toml" -KUBERNETES_CAPABILITY_IDS = { - "kubernetes-core-placement-and-images", - "kubernetes-workspace-isolation", - "kubernetes-supervisor-topology", - "kubernetes-egress-spiffe-and-security", -} -FIELD_GROUP = re.compile( - r"\[openshell\.(?Pgateway|drivers\.kubernetes(?:\.(?P[a-z_]+))?)\]" - r"\.\{(?P[^}]+)\}" -) - - -def load_ledger() -> dict: - with LEDGER.open("rb") as handle: - return tomllib.load(handle) - - -def manifest_kubernetes_fields() -> set[str]: - with CAPABILITIES.open("rb") as handle: - capabilities = tomllib.load(handle)["capabilities"] - fields: set[str] = set() - for capability in capabilities: - if capability["id"] not in KUBERNETES_CAPABILITY_IDS: - continue - for access_path in [ - *capability["origin_main_access_paths"], - *capability["schema_v2_access_paths"], - ]: - for match in FIELD_GROUP.finditer(access_path): - table = match.group("table") - if table == "gateway" and "inherited by Kubernetes" not in access_path: - continue - prefix = ( - f"{match.group('subtable')}." if match.group("subtable") else "" - ) - fields.update( - f"{prefix}{field.strip()}" - for field in match.group("fields").split(",") - ) - return fields - - -def test_kubernetes_ledger_covers_every_manifest_driver_field_once() -> None: - ledger = load_ledger() - coverage = ledger["coverage"] - observed = [field for entry in coverage for field in entry["fields"]] - - assert set(observed) == manifest_kubernetes_fields() - assert len(observed) == len(set(observed)) - - -def test_kubernetes_core_pass_is_paired_and_qualified_checks_stay_blocked() -> None: - ledger = load_ledger() - core = next(entry for entry in ledger["coverage"] if entry["status"] == "pass") - - assert core["id"] == "shared-combined-core" - assert len(core["evidence"]) >= 3 - assert ledger["baseline_commit"] == "74960ebfaeec4673885089ed995fad902459749f" - assert len(ledger["validated_candidate_commit"]) == 40 - - comparison_path = ROOT / ledger["core_comparison"] - comparison = json.loads(comparison_path.read_text()) - assert comparison == { - "accepted": True, - "baseline_commit": ledger["baseline_commit"], - "baseline_success": True, - "candidate_commit": ledger["validated_candidate_commit"], - "candidate_success": True, - "classification": "pass", - "parity": True, - } - with LIVE_RESULTS.open("rb") as handle: - live_results = tomllib.load(handle)["result"] - live_core = next( - result - for result in live_results - if result["id"] == "kubernetes-core-option-parity" - ) - assert live_core["status"] == comparison["classification"] - assert live_core["validated_baseline_commit"] == comparison["baseline_commit"] - assert live_core["validated_candidate_commit"] == comparison["candidate_commit"] - - blocked = [ - entry - for entry in [*ledger["coverage"], *ledger["qualified_value"]] - if entry["status"] == "platform_blocked" - ] - assert blocked - for entry in blocked: - assert entry["owner"] - assert entry["lane"] - assert entry["requirement"] - - -def test_environment_qualified_security_checks_are_not_claimed_by_core() -> None: - ledger = load_ledger() - blocked_fields = { - field - for entry in ledger["coverage"] - if entry["status"] == "platform_blocked" - for field in entry["fields"] - } - qualified = {entry["id"] for entry in ledger["qualified_value"]} - - assert { - "enable_user_namespaces", - "https_proxy", - "proxy_auth_secret_name", - "provider_spiffe_workload_api_socket_path", - "sidecar.proxy_uid", - } <= blocked_fields - assert { - "apparmor-enforcement", - "gpu-resource-combinations", - "image-volume-sideload", - "managed-and-operator-workspace-values", - "runtime-class-isolation", - "sidecar-topology-value", - } <= qualified diff --git a/python/openshell/gateway_schema_v2_live_results_test.py b/python/openshell/gateway_schema_v2_live_results_test.py deleted file mode 100644 index cfd0a01515..0000000000 --- a/python/openshell/gateway_schema_v2_live_results_test.py +++ /dev/null @@ -1,449 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Validate machine-readable schema-v2 live parity results.""" - -from __future__ import annotations - -import hashlib -import json -import re -import subprocess -import tomllib -from pathlib import Path -from typing import Any - -REPO_ROOT = Path(__file__).resolve().parents[2] -RESULTS_PATH = REPO_ROOT / "e2e/configs/gateway/schema-v2-live-results.toml" -CAPABILITY_PATH = REPO_ROOT / "e2e/configs/gateway/schema-v2-capability-parity.toml" -COMPUTE_BOUNDARY_PATH = ( - REPO_ROOT / "e2e/configs/gateway/schema-v2-compute-boundary-comparison.json" -) -CROSS_CUTTING_DISPOSITIONS_PATH = ( - REPO_ROOT / "e2e/configs/gateway/schema-v2-cross-cutting-dispositions.toml" -) - -REQUIRED_HEADER_FIELDS = { - "manifest_version", - "baseline_commit", - "candidate_start_commit", - "result", -} -REQUIRED_STEP_5_IDS = { - "portable-lifecycle-docker", - "portable-lifecycle-kubernetes", - "portable-lifecycle-mxc", - "portable-lifecycle-podman", - "portable-lifecycle-vm", -} -REQUIRED_STEP_6_IDS = { - "gateway-tls-client-auth-policy", - "gateway-wide-process-options", -} -REQUIRED_STEP_7_IDS = { - "docker-driver-option-parity", - "podman-driver-option-parity", - "podman-qualified-security-option-parity", -} -REQUIRED_STEP_8_IDS = { - "kubernetes-core-option-parity", - "kubernetes-qualified-option-parity", -} -REQUIRED_STEP_9_IDS = { - "vm-guest-security-and-spiffe", - "vm-launch-and-resource-configuration", -} -REQUIRED_STEP_10_IDS = {"compute-driver-boundary-parity"} -REQUIRED_STEP_11_IDS = { - "credential-driver-backend-tables", - "credential-driver-selection-and-kek", - "gateway-interceptor-registration", - "gateway-minted-sandbox-jwt", - "inference-control-plane-configuration", - "mtls-user-authentication", - "oidc-bearer-authentication", - "otlp-observability", - "provider-profile-sources", - "supervisor-middleware-registration", - "unsafe-unauthenticated-user-mode", -} -REQUIRED_STEP_12_IDS = { - "debian-package-upgrade", - "homebrew-package-upgrade", - "rpm-package-upgrade", - "snap-package-refresh", -} -EXPECTED_EXECUTED_CANDIDATE_COMMITS = { - "portable-lifecycle-podman": "a3860084d019ed2ac979e3eaa1ddf085a96b773c", - "gateway-wide-process-options": "e6aac1aa7c624c5df43535ab4fbe2bc6f9697dea", - "gateway-tls-client-auth-policy": "e6aac1aa7c624c5df43535ab4fbe2bc6f9697dea", - "podman-driver-option-parity": "e09070d4ba0b30f0ac278fbb9938c1520ecff696", - "kubernetes-core-option-parity": "0f08b5822e4da98c9ced3d4b0f2bf4f30dae28fd", - "compute-driver-boundary-parity": "4a39da510e4d278a24dd60291149519c9a570b46", -} -STEP_10_CANDIDATE_COMMIT = EXPECTED_EXECUTED_CANDIDATE_COMMITS[ - "compute-driver-boundary-parity" -] -STEP_10_REPORT_SHA256 = ( - "3c1297eef6c0a3b22530b980b571bda2d967a39a79a3e3286bcc09e30df84bf3" -) -STEP_10_EVIDENCE_BUNDLES = { - "in_tree": "target/parity/step10-intree-4a39da51", - "external_uds": "target/parity/step10-external-4a39da51", -} -ALLOWED_STATUSES = { - "pass", - "intentional_change", - "regression", - "platform_blocked", -} -BASE_FIELDS = {"id", "step", "capability", "driver", "status", "lane"} -PASS_FIELDS = { - "validated_baseline_commit", - "validated_candidate_commit", - "evidence", -} -BLOCKED_FIELDS = {"owner", "blocker"} - - -def load_toml(path: Path) -> dict[str, Any]: - with path.open("rb") as toml_file: - return tomllib.load(toml_file) - - -def assert_full_sha(value: object, field: str) -> str: - assert isinstance(value, str), f"{field} must be a string" - assert len(value) == 40 and all(char in "0123456789abcdef" for char in value), ( - f"{field} must be a full lowercase SHA" - ) - return value - - -def test_live_results_manifest_is_well_formed() -> None: - manifest = load_toml(RESULTS_PATH) - - assert set(manifest) == REQUIRED_HEADER_FIELDS - assert manifest["manifest_version"] == 1 - baseline = assert_full_sha(manifest["baseline_commit"], "baseline_commit") - assert_full_sha(manifest["candidate_start_commit"], "candidate_start_commit") - assert baseline == load_toml(CAPABILITY_PATH)["baseline_commit"] - - results = manifest["result"] - assert isinstance(results, list) and results - ids: list[str] = [] - for result in results: - assert set(result) >= BASE_FIELDS - result_id = result["id"] - assert isinstance(result_id, str) and result_id.strip() - ids.append(result_id) - assert result["status"] in ALLOWED_STATUSES - assert isinstance(result["step"], int) and 1 <= result["step"] <= 15 - for field in ("capability", "driver", "lane"): - assert isinstance(result[field], str) and result[field].strip(), ( - f"{result_id}: {field} is required" - ) - - assert len(ids) == len(set(ids)) - - -def test_step_5_covers_every_in_tree_compute_driver() -> None: - results = [ - result for result in load_toml(RESULTS_PATH)["result"] if result["step"] == 5 - ] - - assert {result["id"] for result in results} == REQUIRED_STEP_5_IDS - assert {result["driver"] for result in results} == { - "docker", - "kubernetes", - "mxc", - "podman", - "vm", - } - - -def test_executed_results_pin_commits_and_evidence() -> None: - manifest = load_toml(RESULTS_PATH) - executed = { - result["id"]: result - for result in manifest["result"] - if result["status"] in {"pass", "intentional_change"} - } - assert set(executed) == set(EXPECTED_EXECUTED_CANDIDATE_COMMITS) - - for result_id, result in executed.items(): - assert set(result) >= PASS_FIELDS, result_id - assert result["validated_baseline_commit"] == manifest["baseline_commit"] - candidate = assert_full_sha( - result["validated_candidate_commit"], - f"{result_id}.validated_candidate_commit", - ) - assert candidate == EXPECTED_EXECUTED_CANDIDATE_COMMITS[result_id] - assert isinstance(result["evidence"], list) and result["evidence"] - assert all( - isinstance(item, str) and item.strip() for item in result["evidence"] - ) - # These immutable SHAs bind the original live-validation artifacts. - # A later history-preserving source rebase can intentionally make those - # exact execution commits non-ancestors without changing the evidence. - - -def test_step_6_records_gateway_option_and_tls_dispositions() -> None: - results = [ - result for result in load_toml(RESULTS_PATH)["result"] if result["step"] == 6 - ] - - assert {result["id"] for result in results} == REQUIRED_STEP_6_IDS - statuses = {result["id"]: result["status"] for result in results} - assert statuses["gateway-wide-process-options"] == "pass" - assert statuses["gateway-tls-client-auth-policy"] == "intentional_change" - - -def test_step_7_records_driver_option_dispositions() -> None: - results = [ - result for result in load_toml(RESULTS_PATH)["result"] if result["step"] == 7 - ] - - assert {result["id"] for result in results} == REQUIRED_STEP_7_IDS - statuses = {result["id"]: result["status"] for result in results} - assert statuses["podman-driver-option-parity"] == "intentional_change" - assert statuses["docker-driver-option-parity"] == "platform_blocked" - assert statuses["podman-qualified-security-option-parity"] == "platform_blocked" - - -def test_step_8_records_kubernetes_option_dispositions() -> None: - results = [ - result for result in load_toml(RESULTS_PATH)["result"] if result["step"] == 8 - ] - - assert {result["id"] for result in results} == REQUIRED_STEP_8_IDS - statuses = {result["id"]: result["status"] for result in results} - assert statuses["kubernetes-core-option-parity"] == "pass" - assert statuses["kubernetes-qualified-option-parity"] == "platform_blocked" - - -def test_step_9_records_vm_runtime_dispositions() -> None: - results = [ - result for result in load_toml(RESULTS_PATH)["result"] if result["step"] == 9 - ] - - assert {result["id"] for result in results} == REQUIRED_STEP_9_IDS - assert all(result["status"] == "platform_blocked" for result in results) - assert all(result["driver"] == "vm" for result in results) - - -def test_step_10_records_verified_compute_boundary_parity(tmp_path: Path) -> None: - results = [ - result for result in load_toml(RESULTS_PATH)["result"] if result["step"] == 10 - ] - assert {result["id"] for result in results} == REQUIRED_STEP_10_IDS - assert results[0]["status"] == "pass" - - with COMPUTE_BOUNDARY_PATH.open(encoding="utf-8") as report_file: - report = json.load(report_file) - assert report["manifest_version"] == 2 - assert report["baseline_commit"] == load_toml(RESULTS_PATH)["baseline_commit"] - assert report["candidate_commit"] == results[0]["validated_candidate_commit"] - assert report["candidate_commit"] == STEP_10_CANDIDATE_COMMIT - assert report["retained_evidence_bundles"] == STEP_10_EVIDENCE_BUNDLES - assert ( - hashlib.sha256(COMPUTE_BOUNDARY_PATH.read_bytes()).hexdigest() - == STEP_10_REPORT_SHA256 - ) - assert report["classification"] == "pass" - assert report["accepted"] is True - assert all(report["oracle"].values()) - assert all(report["verification"].values()) - - launch_attestations = [] - for topology_name in ("in_tree", "external_uds"): - topology = report[topology_name] - assert topology["classification"] == "pass" - assert topology["parity"] is True - assert topology["accepted"] is True - assert re.fullmatch(r"[0-9a-f]{64}", topology["comparison_sha256"]) - baseline_launch = topology["baseline"]["launch_attestation"] - candidate_launch = topology["candidate"]["launch_attestation"] - for field in ( - "sandbox_image_id", - "sandbox_image_digest", - "sandbox_runtime_image", - "supervisor_base_image", - "supervisor_base_image_id", - "supervisor_base_image_digest", - "supervisor_package_manifest_sha256", - ): - assert baseline_launch[field] == candidate_launch[field] - launch_attestations.extend((baseline_launch, candidate_launch)) - - for variant_name, schema_version in (("baseline", 1), ("candidate", 2)): - variant = topology[variant_name] - assert variant["schema_version"] == schema_version - assert variant["success"] is True - assert variant["artifacts_verified"] is True - assert variant["raw_output_verified"] is True - assert all( - re.fullmatch(r"[0-9a-f]{64}", digest) - for digest in variant["artifact_sha256"].values() - ) - assert all( - re.fullmatch(r"[0-9a-f]{64}", digest) - for digest in variant["raw_evidence_sha256"].values() - ) - launch = variant["launch_attestation"] - supervisor_digest = launch["supervisor_image_digest"] - assert re.fullmatch(r"sha256:[0-9a-f]{64}", supervisor_digest) - assert launch["supervisor_runtime_image"].endswith(f"@{supervisor_digest}") - sandbox_digest = launch["sandbox_image_digest"] - assert re.fullmatch(r"sha256:[0-9a-f]{64}", sandbox_digest) - assert launch["sandbox_image_request"] == launch["sandbox_runtime_image"] - assert launch["sandbox_runtime_image"].endswith(f"@{sandbox_digest}") - - for field in ( - "sandbox_image_id", - "sandbox_image_digest", - "sandbox_runtime_image", - "supervisor_base_image", - "supervisor_base_image_id", - "supervisor_base_image_digest", - "supervisor_base_runtime_image", - "supervisor_package_manifest_sha256", - "supervisor_dockerfile_sha256_before_execution", - ): - assert len({launch[field] for launch in launch_attestations}) == 1, field - - external_sockets = [] - for variant_name in ("baseline", "candidate"): - external_variant = report["external_uds"][variant_name] - external_launch = external_variant["launch_attestation"] - assert external_launch["compute_driver_transport"] == "remote_uds" - assert external_launch["external_compute_driver"] is True - assert external_launch["external_driver_grpc_endpoint"].startswith("https://") - assert external_launch["external_driver_host_gateway_ip"] == "host-gateway" - assert external_launch["external_driver_userns"] is None - assert external_launch["external_driver_spiffe"] is False - assert external_launch["external_driver_proxy"] is False - assert external_launch["external_driver_app_armor"] is False - driver_environment = external_launch["external_driver_environment"] - assert driver_environment["OPENSHELL_COMPUTE_DRIVER_SOCKET"] - assert driver_environment["OPENSHELL_PODMAN_SOCKET"] - assert driver_environment["OPENSHELL_GRPC_ENDPOINT"].startswith("https://") - assert driver_environment["OPENSHELL_ENABLE_BIND_MOUNTS"] is True - for tls_field in ( - "OPENSHELL_PODMAN_TLS_CA", - "OPENSHELL_PODMAN_TLS_CERT", - "OPENSHELL_PODMAN_TLS_KEY", - ): - assert re.fullmatch( - r"[0-9a-f]{64}", driver_environment[tls_field]["sha256"] - ) - external_sockets.append(driver_environment["OPENSHELL_COMPUTE_DRIVER_SOCKET"]) - assert f"{variant_name}.driver.log" in external_variant["raw_evidence_sha256"] - assert f"{variant_name}.exec.stdout" in external_variant["raw_evidence_sha256"] - assert len(set(external_sockets)) == 2 - - evidence_paths = { - name: REPO_ROOT / relative_path - for name, relative_path in STEP_10_EVIDENCE_BUNDLES.items() - } - present = {name: path.is_dir() for name, path in evidence_paths.items()} - assert len(set(present.values())) == 1, "Step 10 retained evidence is incomplete" - if all(present.values()): - reproduced = tmp_path / "schema-v2-compute-boundary-comparison.json" - subprocess.run( - [ - "python3", - str(REPO_ROOT / "e2e/parity/verify-results.py"), - "--baseline-sha", - report["baseline_commit"], - "--candidate-sha", - STEP_10_CANDIDATE_COMMIT, - "--in-tree", - STEP_10_EVIDENCE_BUNDLES["in_tree"], - "--external-uds", - STEP_10_EVIDENCE_BUNDLES["external_uds"], - "--output", - str(reproduced), - ], - cwd=REPO_ROOT, - check=True, - ) - assert reproduced.read_bytes() == COMPUTE_BOUNDARY_PATH.read_bytes() - - -def test_step_11_records_cross_cutting_live_lane_dispositions() -> None: - results = { - result["id"]: result - for result in load_toml(RESULTS_PATH)["result"] - if result["step"] == 11 - } - dispositions = { - entry["id"]: entry - for entry in load_toml(CROSS_CUTTING_DISPOSITIONS_PATH)["capabilities"] - } - - assert set(results) == REQUIRED_STEP_11_IDS - assert set(dispositions) == REQUIRED_STEP_11_IDS - for result_id, result in results.items(): - disposition = dispositions[result_id] - assert result["status"] == "platform_blocked" - for field in ("status", "owner", "lane", "blocker"): - assert result[field] == disposition[field] - - -def test_step_12_keeps_real_package_upgrade_lanes_blocked() -> None: - results = [ - result for result in load_toml(RESULTS_PATH)["result"] if result["step"] == 12 - ] - - assert {result["id"] for result in results} == REQUIRED_STEP_12_IDS - assert all(result["status"] == "platform_blocked" for result in results) - by_id = {result["id"]: result for result in results} - - rpm = by_id["rpm-package-upgrade"] - assert rpm["owner"] == "OpenShell RPM package upgrade CI lane" - assert rpm["lane"] == "fedora-rpm-prior-release-upgrade" - assert "prior RPM" in rpm["blocker"] - assert "installed and upgraded" in rpm["blocker"] - - debian = by_id["debian-package-upgrade"] - assert debian["owner"] == "OpenShell Debian package upgrade CI lane" - assert debian["lane"] == "ubuntu-debian-prior-release-upgrade" - assert "installed prior Debian artifact" in debian["blocker"] - assert "source-tree tests" in debian["blocker"] - - snap = by_id["snap-package-refresh"] - assert snap["owner"] == "OpenShell Snap refresh CI lane" - assert snap["lane"] == "ubuntu-snap-prior-release-refresh" - assert "refresh confined Snap revisions" in snap["blocker"] - assert "source-tree tests" in snap["blocker"] - - homebrew = by_id["homebrew-package-upgrade"] - assert homebrew["owner"] == "OpenShell macOS Homebrew package upgrade CI lane" - assert homebrew["lane"] == "macos-homebrew-prior-release-upgrade" - assert "prior-release upgrade" in homebrew["blocker"] - assert "Linux host" in homebrew["blocker"] - - -def test_platform_blocked_results_name_owner_lane_and_blocker() -> None: - for result in load_toml(RESULTS_PATH)["result"]: - if result["status"] != "platform_blocked": - continue - - assert set(result) >= BLOCKED_FIELDS, result["id"] - assert isinstance(result["owner"], str) and result["owner"].strip() - assert isinstance(result["blocker"], str) and len(result["blocker"]) >= 80 - - -def test_step_5_records_only_executed_podman_as_pass() -> None: - statuses = { - result["driver"]: result["status"] - for result in load_toml(RESULTS_PATH)["result"] - if result["step"] == 5 - } - - assert statuses["podman"] == "pass" - assert all( - status == "platform_blocked" - for driver, status in statuses.items() - if driver != "podman" - ) diff --git a/python/openshell/gateway_schema_v2_parity_gap_dispositions_test.py b/python/openshell/gateway_schema_v2_parity_gap_dispositions_test.py deleted file mode 100644 index 76c8f08bd4..0000000000 --- a/python/openshell/gateway_schema_v2_parity_gap_dispositions_test.py +++ /dev/null @@ -1,195 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Validate schema-v2 parity-gap dispositions and release blockers.""" - -from __future__ import annotations - -import tomllib -from pathlib import Path -from typing import Any - -REPO_ROOT = Path(__file__).resolve().parents[2] -GAP_LEDGER_PATH = ( - REPO_ROOT / "e2e/configs/gateway/schema-v2-parity-gap-dispositions.toml" -) -LIVE_RESULTS_PATH = REPO_ROOT / "e2e/configs/gateway/schema-v2-live-results.toml" - -REQUIRED_HEADER_FIELDS = { - "ledger_version", - "issue", - "baseline_commit", - "candidate_start_commit", - "gaps", -} -REQUIRED_GAP_FIELDS = { - "id", - "severity", - "parity_relation", - "disposition", - "origin_main_behavior", - "candidate_behavior", - "impact", - "resolution", - "validation", - "owner_step", -} -REQUIRED_GAP_IDS = { - "debian-snap-v1-upgrade", - "gateway-owned-guest-tls", - "generated-e2e-selector-shape", - "legacy-environment-selector-upgrade", - "multi-driver-runtime-loss", - "rpm-exact-default-migration", - "tls-require-client-auth-ignored", - "unselected-driver-validation-claim", -} -ALLOWED_SEVERITIES = {"blocker", "major", "minor", "none"} -ALLOWED_RELATIONS = { - "documentation_gap", - "non_finding", - "preexisting_inaccessible_option", - "regression", - "upgrade_regression", -} -ALLOWED_DISPOSITIONS = { - "documentation_fix_required", - "must_fix_before_parity", - "must_fix_before_release_gate", - "must_fix_or_remove_claim", - "no_action", - "resolved", -} - - -def load_ledger() -> dict[str, Any]: - with GAP_LEDGER_PATH.open("rb") as ledger_file: - return tomllib.load(ledger_file) - - -def test_gap_disposition_ledger_is_complete_and_well_formed() -> None: - ledger = load_ledger() - - assert set(ledger) == REQUIRED_HEADER_FIELDS - assert ledger["ledger_version"] == 1 - assert ledger["issue"] == 2792 - assert ledger["baseline_commit"] == "74960ebfaeec4673885089ed995fad902459749f" - assert ledger["candidate_start_commit"] == ( - "8c868e430e9cd3284d7e274628419ab484ebcee0" - ) - - gaps = ledger["gaps"] - assert isinstance(gaps, list) and gaps - ids: list[str] = [] - for gap in gaps: - assert set(gap) == REQUIRED_GAP_FIELDS - gap_id = gap["id"] - assert isinstance(gap_id, str) and gap_id.strip() - ids.append(gap_id) - assert gap["severity"] in ALLOWED_SEVERITIES - assert gap["parity_relation"] in ALLOWED_RELATIONS - assert gap["disposition"] in ALLOWED_DISPOSITIONS - assert isinstance(gap["owner_step"], int) and 1 <= gap["owner_step"] <= 15 - for field in ( - "origin_main_behavior", - "candidate_behavior", - "impact", - "resolution", - "validation", - ): - assert isinstance(gap[field], str) and gap[field].strip(), ( - f"{gap_id}: {field} is required" - ) - - assert len(ids) == len(set(ids)) - assert set(ids) == REQUIRED_GAP_IDS - - -def test_every_blocker_has_a_required_fix_and_validation() -> None: - for gap in load_ledger()["gaps"]: - if gap["severity"] != "blocker": - continue - - assert gap["disposition"].startswith("must_fix") - assert gap["owner_step"] in {6, 12} - assert len(gap["resolution"]) >= 80 - assert len(gap["validation"]) >= 80 - - -def test_non_findings_do_not_require_product_changes() -> None: - for gap in load_ledger()["gaps"]: - if gap["parity_relation"] == "non_finding": - assert gap["severity"] == "none" - assert gap["disposition"] == "no_action" - - -def test_security_sensitive_tls_gap_records_reviewed_resolution() -> None: - tls_gap = next( - gap - for gap in load_ledger()["gaps"] - if gap["id"] == "tls-require-client-auth-ignored" - ) - - assert tls_gap["parity_relation"] == "preexisting_inaccessible_option" - assert tls_gap["disposition"] == "resolved" - assert "Security review" in tls_gap["resolution"] - assert "rejects require_client_auth" in tls_gap["candidate_behavior"] - - -def test_step_6_gap_dispositions_are_resolved() -> None: - step_6_gaps = [gap for gap in load_ledger()["gaps"] if gap["owner_step"] == 6] - - assert {gap["id"] for gap in step_6_gaps} == { - "legacy-environment-selector-upgrade", - "tls-require-client-auth-ignored", - "unselected-driver-validation-claim", - } - assert all(gap["severity"] == "none" for gap in step_6_gaps) - assert all(gap["disposition"] == "resolved" for gap in step_6_gaps) - - -def test_step_12_product_gaps_are_resolved_without_claiming_live_package_parity() -> ( - None -): - step_12_gaps = [gap for gap in load_ledger()["gaps"] if gap["owner_step"] == 12] - assert {gap["id"] for gap in step_12_gaps} == { - "debian-snap-v1-upgrade", - "rpm-exact-default-migration", - } - - debian_snap = next( - gap for gap in step_12_gaps if gap["id"] == "debian-snap-v1-upgrade" - ) - assert debian_snap["severity"] == "none" - assert debian_snap["disposition"] == "resolved" - assert "source-free, read-only preflight" in debian_snap["candidate_behavior"] - assert "fail closed" in debian_snap["resolution"] - assert "manual migration" in debian_snap["resolution"] - assert "no safe default-provenance marker" in debian_snap["resolution"] - assert "platform-blocked" in debian_snap["validation"] - - with LIVE_RESULTS_PATH.open("rb") as results_file: - package_results = { - result["id"]: result - for result in tomllib.load(results_file)["result"] - if result["step"] == 12 - } - for result_id in ( - "debian-package-upgrade", - "homebrew-package-upgrade", - "rpm-package-upgrade", - "snap-package-refresh", - ): - assert package_results[result_id]["status"] == "platform_blocked" - - -def test_legacy_environment_resolution_preserves_singular_semantics() -> None: - legacy_gap = next( - gap - for gap in load_ledger()["gaps"] - if gap["id"] == "legacy-environment-selector-upgrade" - ) - - assert "one non-empty OPENSHELL_DRIVERS value" in legacy_gap["resolution"] - assert "rejects multiple values" in legacy_gap["resolution"] - assert "conflicting canonical and legacy values" in legacy_gap["resolution"] diff --git a/tasks/parity.toml b/tasks/parity.toml deleted file mode 100644 index 2bc139a53a..0000000000 --- a/tasks/parity.toml +++ /dev/null @@ -1,33 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -["test:e2e-parity"] -description = "Run deterministic schema-v1/schema-v2 parity harness tests" -run = "bash e2e/parity/test.sh" -run_windows = "powershell -NoProfile -ExecutionPolicy Bypass -File tasks/scripts/run-git-bash.ps1 e2e/parity/test.sh" -hide = true - -["e2e:parity:podman"] -description = "Compare frozen schema-v1 and current schema-v2 conformance against Podman and retain a fresh evidence directory (opt-in live test)" -run = "bash e2e/parity/run.sh --driver podman" - -["e2e:parity:podman-options"] -description = "Compare paired Podman sandbox option semantics and retain a fresh evidence directory (opt-in live test)" -run = "bash e2e/parity/run.sh --driver podman --scenario podman-options" - -["e2e:parity:podman-external-driver"] -description = "Compare paired external Podman lifecycle semantics and retain a fresh evidence directory (opt-in live test)" -run = "bash e2e/parity/run.sh --driver podman --scenario external-driver" - -["e2e:parity:gateway-options"] -description = "Compare process-level gateway option behavior across schema v1 and v2 (opt-in live test)" -run = "bash e2e/parity/gateway-options.sh" - -["test:e2e-parity:kubernetes-options"] -description = "Run deterministic Kubernetes schema parity harness tests" -run = "bash e2e/parity/kubernetes-options-test.sh" -hide = true - -["e2e:parity:kubernetes-options"] -description = "Compare Kubernetes driver options on an explicitly supplied disposable kind cluster" -run = "bash e2e/parity/kubernetes-options.sh" diff --git a/tasks/scripts/test-gateway-config.sh b/tasks/scripts/test-gateway-config.sh index 9541aa68fc..bc36c9c55c 100755 --- a/tasks/scripts/test-gateway-config.sh +++ b/tasks/scripts/test-gateway-config.sh @@ -31,4 +31,47 @@ CAPTURED_CONFIG="${WORK}/generated.toml" PATH="${WORK}/bin:${PATH}" KUBERNETES_S printf '%s\n' 'import sys, tomllib' 'from pathlib import Path' 'config = tomllib.loads(Path(sys.argv[1]).read_text())' 'gateway = config["openshell"]["gateway"]' 'driver = config["openshell"]["drivers"]["kubernetes"]' 'assert config["openshell"]["version"] == 2' 'assert gateway["compute_driver"] == "kubernetes"' 'assert "compute_drivers" not in gateway' 'assert driver["image_pull_policy"] == "if_not_present"' 'assert driver["grpc_endpoint"] == "https://callback.example.test:9443"' > "${WORK}/check_generated.py" "${UV}" run --no-project python "${WORK}/check_generated.py" "${CAPTURED_CONFIG}" + +# Keep the Podman E2E generator on the current gateway schema and preserve the +# in-tree versus external-driver ownership boundary without starting Podman. +# shellcheck source=e2e/support/gateway-common.sh +source "${ROOT}/e2e/support/gateway-common.sh" +# shellcheck source=e2e/support/podman-gateway-config.sh +source "${ROOT}/e2e/support/podman-gateway-config.sh" + +mkdir -p "${WORK}/pki/client" "${WORK}/jwt" +e2e_write_podman_gateway_config \ + "${WORK}/podman.toml" "${ROOT}" "${WORK}/pki" "${WORK}/jwt" \ + test-gateway 0 "${WORK}/driver.sock" test-network 18181 \ + workload:test 15 supervisor:test sandbox:test "${WORK}/spiffe.sock" \ + "${WORK}/podman.sock" 0 "" +e2e_write_podman_gateway_config \ + "${WORK}/podman-external.toml" "${ROOT}" "${WORK}/pki" "${WORK}/jwt" \ + test-gateway 1 "${WORK}/driver.sock" test-network 18181 \ + workload:test 15 supervisor:test sandbox:test "${WORK}/spiffe.sock" \ + "${WORK}/podman.sock" 0 "" + +printf '%s\n' \ + 'import sys, tomllib' \ + 'from pathlib import Path' \ + 'internal = tomllib.loads(Path(sys.argv[1]).read_text())' \ + 'external = tomllib.loads(Path(sys.argv[2]).read_text())' \ + 'assert internal["openshell"]["version"] == 2' \ + 'gateway = internal["openshell"]["gateway"]' \ + 'driver = internal["openshell"]["drivers"]["podman"]' \ + 'assert gateway["compute_driver"] == "podman"' \ + 'assert gateway["guest_tls_ca"].endswith("/pki/ca.crt")' \ + 'assert driver["default_image"] == "workload:test"' \ + 'assert driver["image_pull_policy"] == "if_not_present"' \ + 'assert driver["supervisor_image"] == "supervisor:test"' \ + 'assert driver["sandbox_runtime_image"] == "sandbox:test"' \ + 'assert driver["provider_spiffe_workload_api_socket"].endswith("/spiffe.sock")' \ + 'assert driver["socket_path"].endswith("/podman.sock")' \ + 'assert driver["resource_admission"] == {"enabled": False}' \ + 'external_driver = external["openshell"]["drivers"]["podman"]' \ + 'assert external["openshell"]["gateway"]["guest_tls_ca"].endswith("/pki/ca.crt")' \ + 'assert external_driver == {"socket_path": sys.argv[3]}' \ + >"${WORK}/check_podman_generated.py" +"${UV}" run --no-project python "${WORK}/check_podman_generated.py" \ + "${WORK}/podman.toml" "${WORK}/podman-external.toml" "${WORK}/driver.sock" echo "gateway generated-TOML tests passed" diff --git a/tasks/test.toml b/tasks/test.toml index c45826f1db..8a90f3c084 100644 --- a/tasks/test.toml +++ b/tasks/test.toml @@ -15,7 +15,6 @@ depends = [ "test:gateway-pull-policy", "test:e2e-image-overrides", "test:gateway-config", - "test:e2e-parity", "test:packaging-assets", "test:qualification-summary", "test:codex-security-release-range", From 5698c4f746a1f8356b0895a07e35dc98a14fa8af Mon Sep 17 00:00:00 2001 From: Simon Scatton <44714756+SDAChess@users.noreply.github.com> Date: Thu, 1 Oct 2026 14:00:45 +0000 Subject: [PATCH 20/33] fix(ci): qualify protobuf compatibility by release train (#4049) * feat(ci): detect breaking protobuf changes Compare the proto module against the PR or merge-group base and report Buf violations in Branch Checks. Add local reproduction and fixture coverage. Closes #3794 Signed-off-by: Mrunal Patel * fix(ci): pin protobuf check container image Signed-off-by: Mrunal Patel * fix(ci): qualify protobuf compatibility by release train Signed-off-by: Simon Scatton * refactor(ci): reuse protobuf compatibility action Signed-off-by: Simon Scatton * refactor(ci): run protobuf checks as a Nix app with one ref Signed-off-by: Simon Scatton --------- Signed-off-by: Mrunal Patel Signed-off-by: Simon Scatton Co-authored-by: Mrunal Patel --- .agents/skills/watch-github-actions/SKILL.md | 12 ++ .../check-protobuf-compatibility/action.yml | 22 +++ .github/actions/pr-gate/action.yml | 7 + .github/workflows/branch-checks.yml | 21 +++ .github/workflows/release-tag.yml | 19 +++ CI.md | 53 ++++++- flake.nix | 14 ++ tasks/scripts/check_proto_compatibility.py | 145 ++++++++++++++++++ .../scripts/generate-qualification-summary.sh | 7 +- tasks/scripts/test-qualification-summary.sh | 1 + 10 files changed, 297 insertions(+), 4 deletions(-) create mode 100644 .github/actions/check-protobuf-compatibility/action.yml create mode 100644 tasks/scripts/check_proto_compatibility.py diff --git a/.agents/skills/watch-github-actions/SKILL.md b/.agents/skills/watch-github-actions/SKILL.md index 5d926cc647..5437a1a882 100644 --- a/.agents/skills/watch-github-actions/SKILL.md +++ b/.agents/skills/watch-github-actions/SKILL.md @@ -132,6 +132,18 @@ not substitute the current `main` tip or the event's older PR base SHA. Merge groups and manual runs use their explicit baseline. Findings are reported by `Reject new high or critical findings`; distinguish those from scanner failures. +For `Protobuf Compatibility`, check the logged train and comparison baseline. +Branch Checks compares the prospective merge tree with its target; Release Tag +compares the tagged candidate with the previous stable release. Both use +the shared `check-protobuf-compatibility` action with `nix run .#check-protobuf-compatibility -- `. +During `0.x`, a minor train permits compatibility findings +as warnings; a patch train or no active train rejects them. Compare the current +train's version with the latest stable release; commit messages are irrelevant. +Compilation, baseline, and tool errors remain fatal. The `protobuf_compatibility` suite participates in +the `release-tag-v1` qualification profile. Failed qualification prevents stable +publication but still allows pre-release artifacts to publish with the failure +recorded. + View logs for a specific run: ```bash diff --git a/.github/actions/check-protobuf-compatibility/action.yml b/.github/actions/check-protobuf-compatibility/action.yml new file mode 100644 index 0000000000..f11af91a5e --- /dev/null +++ b/.github/actions/check-protobuf-compatibility/action.yml @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Check Protobuf Compatibility +description: Check protobuf compatibility against the target or latest stable release using the release train policy. Requires a checkout with full history and tags. + +inputs: + ref: + description: PR or merge-group target branch/commit, or release tag to qualify. + required: true + +runs: + using: composite + steps: + - uses: ./.github/actions/setup-nix + + - name: Check protobuf compatibility + shell: bash + env: + CHECK_REF: ${{ inputs.ref }} + run: | + nix run .#check-protobuf-compatibility --no-write-lock-file -- "$CHECK_REF" diff --git a/.github/actions/pr-gate/action.yml b/.github/actions/pr-gate/action.yml index e21fb488eb..c696f26b03 100644 --- a/.github/actions/pr-gate/action.yml +++ b/.github/actions/pr-gate/action.yml @@ -16,6 +16,9 @@ outputs: should_run: description: "true if the workflow should proceed, false otherwise" value: ${{ steps.gate.outputs.should_run }} + base_sha: + description: "Target branch commit SHA for a mirrored pull request, or empty for other events" + value: ${{ steps.gate.outputs.base_sha }} labels_json: description: "JSON array of PR label names for push-triggered mirror runs, or [] otherwise" value: ${{ steps.gate.outputs.labels_json }} @@ -40,16 +43,19 @@ runs: if [ "$EVENT_NAME" != "push" ]; then echo "labels_json=[]" >> "$GITHUB_OUTPUT" echo "should_run=true" >> "$GITHUB_OUTPUT" + echo "base_sha=" >> "$GITHUB_OUTPUT" exit 0 fi if [ "$GET_PR_INFO_OUTCOME" != "success" ]; then echo "labels_json=[]" >> "$GITHUB_OUTPUT" echo "should_run=false" >> "$GITHUB_OUTPUT" + echo "base_sha=" >> "$GITHUB_OUTPUT" exit 0 fi head_sha="$(jq -r '.head.sha' <<< "$PR_INFO")" + base_sha="$(jq -r '.base.sha // empty' <<< "$PR_INFO")" labels_json="$(jq -c '[.labels[].name]' <<< "$PR_INFO")" if [ -z "$REQUIRED_LABEL" ]; then has_label=true @@ -67,3 +73,4 @@ runs: echo "labels_json=$labels_json" >> "$GITHUB_OUTPUT" echo "should_run=$should_run" >> "$GITHUB_OUTPUT" + echo "base_sha=$base_sha" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/branch-checks.yml b/.github/workflows/branch-checks.yml index 04f1dc87ea..5cb545a571 100644 --- a/.github/workflows/branch-checks.yml +++ b/.github/workflows/branch-checks.yml @@ -30,12 +30,33 @@ jobs: pull-requests: read outputs: should_run: ${{ steps.gate.outputs.should_run }} + base_sha: ${{ steps.gate.outputs.base_sha }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - id: gate uses: ./.github/actions/pr-gate + protobuf-compatibility: + name: Protobuf Compatibility + needs: pr_metadata + if: needs.pr_metadata.outputs.should_run == 'true' + runs-on: linux-amd64-cpu8 + timeout-minutes: 15 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - uses: ./.github/actions/check-protobuf-compatibility + with: + ref: >- + ${{ github.event_name == 'workflow_dispatch' + && format('refs/remotes/origin/{0}', github.event.repository.default_branch) + || github.event.merge_group.base_sha + || needs.pr_metadata.outputs.base_sha }} + mise-lockfile: name: mise Lockfile needs: pr_metadata diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 3e18a0936f..5ca1d33d98 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -215,11 +215,28 @@ jobs: checkout-ref: ${{ needs.compute-versions.outputs.source_sha }} conformance-artifact-prefix: openshell-conformance + protobuf-compatibility: + name: Protobuf Compatibility + needs: compute-versions + runs-on: linux-amd64-cpu8 + timeout-minutes: 15 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + ref: ${{ needs.compute-versions.outputs.source_sha }} + + - uses: ./.github/actions/check-protobuf-compatibility + with: + ref: refs/tags/${{ env.RELEASE_TAG }} + qualification-result: name: Release Qualification if: always() needs: - compute-versions + - protobuf-compatibility - security - conformance-integration - feature-specific-integration @@ -245,6 +262,7 @@ jobs: DOCKER_E2E_RESULT: ${{ needs.docker-e2e.result }} FEATURE_INTEGRATION_RESULT: ${{ needs.feature-specific-integration.result }} IS_PRERELEASE: ${{ needs.compute-versions.outputs.is_prerelease }} + PROTO_COMPATIBILITY_RESULT: ${{ needs.protobuf-compatibility.result }} SECURITY_RESULT: ${{ needs.security.result }} SOURCE_SHA: ${{ needs.compute-versions.outputs.source_sha }} VM_E2E_RESULT: ${{ needs.vm-e2e.result }} @@ -269,6 +287,7 @@ jobs: echo echo "| Suite | Result |" echo "| --- | --- |" + echo "| Protobuf API compatibility | ${PROTO_COMPATIBILITY_RESULT} |" echo "| Security | ${SECURITY_RESULT} |" echo "| Conformance integration | ${CONFORMANCE_RESULT} |" echo "| Feature integration | ${FEATURE_INTEGRATION_RESULT} |" diff --git a/CI.md b/CI.md index 6c07eac087..33b9d748a6 100644 --- a/CI.md +++ b/CI.md @@ -17,6 +17,55 @@ Manual admission does not change the bot's automatic trust policy for ready PRs. Merge queue validation is a second integration gate for `main`. After a PR has passed the required PR-head statuses, a maintainer adds it to the merge queue. GitHub creates a temporary merge-group branch that combines the latest `main`, the queued PR, and any earlier queued PRs. The same required `OpenShell / ...` status contexts are then published against the merge-group SHA before GitHub merges it. +### Protobuf API compatibility + +`Protobuf Compatibility` runs in Branch Checks and Release Tag through the shared +`check-protobuf-compatibility` action and Nix app. The app supplies Python, Buf, +and Git from the flake lockfile. +It uses Buf's `FILE` policy for the `proto/` +module, covering SDK descriptors and extension contracts. Storage-only protobufs +remain subject to their separate durability checks. + +Branch Checks compares the prospective merge tree with the PR target commit, +so additions on the target do not look like deletions in an outdated PR. Merge +queues use their base commit. Both paths resolve the train from tags reachable +from that target, preventing a PR from selecting its own train. The checkout +must contain full history and tags; the checker leaves HEAD and working files +unchanged. + +The current train's version is compared with the latest stable release. During +`0.x`, a minor increment permits breaking changes: for example, `0.2.0-pre.N` +after stable `0.1.2`. A patch increment such as `0.1.3-pre.N` rejects them. +Commit messages do not affect this decision. No active train also rejects +breaking changes. Allowed findings remain visible as warnings; schema errors, +missing baselines, merge conflicts, and tool failures always fail the check. + +Release Tag compares the tagged candidate cumulatively against the previous +stable release, using the same minor-versus-patch policy. Its result is part +of the qualification profile: failure blocks stable publication, while a +pre-release can still publish with failed qualification recorded. This checks +protobuf compatibility; SDK/configuration compatibility and migration review +remain separate qualification work. + +Fetch the target and tags to check committed branch changes locally, replacing +`origin/main` for another target: + +```shell +git fetch origin main --tags +nix run .#check-protobuf-compatibility -- origin/main +``` + +The same command accepts a release tag to qualify it against the previous stable +release. Branch names and commit SHAs select the branch comparison instead: + +```shell +nix run .#check-protobuf-compatibility -- refs/tags/v0.2.0-pre.1 +``` + +For an intentional minor-train incompatibility, record the Buf finding, +linked issue, consumer impact, and migration plan in the PR. Keep the finding +visible; do not disable the job or add a broad Buf ignore rule. + Windows PR checks are opt-in: add `test:windows`, then select **Re-run all jobs** on the current Windows MSVC run. Subsequent mirrored commits run them automatically. Windows checks are not required for merging and do not run in merge queues. @@ -172,7 +221,7 @@ jobs: ``` Set `needs: security` on a downstream promotion job to require successful scans. -The tagged release workflow records security and integration outcomes in a +The tagged release workflow records protobuf, security, and integration outcomes in a qualification job after publishing its commit-addressed OCI images. A failed check remains visible in the workflow, but pre-release artifact assembly and publication continue. Stable publication currently requires the implemented @@ -431,7 +480,7 @@ These workflows run after merge to publish dev/tagged artifacts and verify them. | File | Role | |---|---| | `.github/workflows/release-dev.yml` | Publishes the rolling `dev` build on every push to `main`. Builds gateway, sandbox, and supervisor images and binaries, packages, wheels, and pushes the Helm chart as `oci://ghcr.io/nvidia/openshell/helm-chart:0.0.0-dev` (plus an immutable `0.0.0-dev.` pin). Also dispatchable manually. | -| `.github/workflows/release-tag.yml` | Publishes tagged stable releases and manually dispatched pre-releases. Its automatic tag trigger excludes `-pre.*`. Security and integration failures do not block pre-release artifact publication. Stable publication requires the currently implemented qualification profile to pass; the summary identifies the remaining RFC 0014 coverage. | +| `.github/workflows/release-tag.yml` | Publishes tagged stable releases and manually dispatched pre-releases. Its automatic tag trigger excludes `-pre.*`. Protobuf, security, and integration failures do not block pre-release artifact publication. Stable publication requires the currently implemented qualification profile to pass; the summary identifies the remaining RFC 0014 coverage. | | `.github/workflows/release-canary.yml` | Smoke-tests published dev artifacts in the `macos`, `ubuntu-deb`, `ubuntu-snap-system-docker`, `fedora`, and `kubernetes` (kind + Helm) jobs. Each job reaches its gateway and creates, exercises, and deletes a sandbox. The Snap lanes verify a compatible system Docker lifecycle and `ubuntu-snap-docker-preflight` tests fail-fast behavior when Docker is absent or supplied by the Docker snap. It runs automatically after `Release Dev` succeeds and supports manual dispatch (`gh workflow run release-canary.yml --ref `). See the `test-release-canary` skill for the playbook and local kind reproduction. | ## Required status contexts diff --git a/flake.nix b/flake.nix index 94839accf0..0635ff84bc 100644 --- a/flake.nix +++ b/flake.nix @@ -125,9 +125,23 @@ firmwarePkgs = tmachineRuntimePkgs; }; artifacts = pkgs.callPackage ./tests/artifacts.nix { inherit rustToolchain toolchains; }; + checkProtobufCompatibility = pkgs.writeShellApplication { + name = "check-protobuf-compatibility"; + runtimeInputs = [ + pkgs.buf + pkgs.git + ]; + text = '' + exec ${pkgs.python3}/bin/python3 ${./tasks/scripts}/check_proto_compatibility.py "$@" + ''; + }; in { apps = { + check-protobuf-compatibility = { + type = "app"; + program = "${checkProtobufCompatibility}/bin/check-protobuf-compatibility"; + }; build-artifacts = { type = "app"; program = "${artifacts.all}/bin/build-artifacts"; diff --git a/tasks/scripts/check_proto_compatibility.py b/tasks/scripts/check_proto_compatibility.py new file mode 100644 index 0000000000..8a2ba37134 --- /dev/null +++ b/tasks/scripts/check_proto_compatibility.py @@ -0,0 +1,145 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Check protobuf compatibility under the current, tag-defined release train.""" + +import argparse +import io +import os +import subprocess +import sys +import tarfile +import tempfile +from pathlib import Path + +from release import _parse_prerelease_tag, _parse_semver_tag + + +def git(*args: str) -> str: + return subprocess.run( + ["git", *args], check=True, capture_output=True, text=True + ).stdout.strip() + + +def train_policy(ref: str, release: str | None) -> tuple[str, str, bool]: + tags = git("tag", "--merged", ref, "--list", "v*").splitlines() + stable = sorted( + (version, tag) + for tag in tags + if tag != release and (version := _parse_semver_tag(tag)) + ) + if release is None: + prereleases = sorted( + (version, tag) for tag in tags if (version := _parse_prerelease_tag(tag)) + ) + if not prereleases or (stable and prereleases[-1][0][:3] <= stable[-1][0]): + return "", "none", False + release = prereleases[-1][1] + + if not stable: + raise ValueError("No previous stable release baseline is available.") + previous, baseline = stable[-1] + prerelease = _parse_prerelease_tag(release) + version = prerelease[:3] if prerelease else _parse_semver_tag(release) + if version is None or not release.startswith("v"): + raise ValueError(f"Invalid release tag: {release}") + if version <= previous: + raise ValueError(f"Release {release} must be newer than stable {baseline}.") + train = "v" + ".".join(map(str, version)) + allows_breaks = version[:2] > previous[:2] + return baseline, f"{train} (latest stable: {baseline})", allows_breaks + + +def export_proto(ref: str, destination: Path) -> None: + archive = subprocess.check_output( + ["git", "archive", ref, "--", "buf.yaml", "proto"] + ) + with tarfile.open(fileobj=io.BytesIO(archive)) as snapshot: + snapshot.extractall(destination, filter="data") + + +def compare(candidate: str, baseline: str, allows_breaks: bool) -> int: + with tempfile.TemporaryDirectory(prefix="openshell-proto-") as directory: + root = Path(directory) + export_proto(baseline, root / "before") + export_proto(candidate, root / "after") + error_format = ( + "github-actions" if os.environ.get("GITHUB_ACTIONS") == "true" else "text" + ) + # Buf also returns 100 for compiler diagnostics. Validate both snapshots + # before treating that status from `breaking` as an allowed API change. + for snapshot in ("before", "after"): + subprocess.run( + [ + "buf", + "build", + "proto", + "--error-format", + error_format, + "-o", + os.devnull, + ], + cwd=root / snapshot, + check=True, + ) + result = subprocess.run( + [ + "buf", + "breaking", + "proto", + "--against", + str(root / "before" / "proto"), + "--config", + "buf.yaml", + "--error-format", + error_format, + ], + cwd=root / "after", + capture_output=True, + text=True, + check=False, + ) + diagnostics = result.stdout + result.stderr + if result.returncode == 100 and allows_breaks: + print(diagnostics.replace("::error ", "::warning "), end="") + print( + "Breaking changes allowed by the version increment; review migration guidance." + ) + return 0 + print(diagnostics, end="") + return result.returncode + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("ref", help="Target branch/commit, or release tag to qualify") + args = parser.parse_args() + ref = git( + "rev-parse", "--symbolic-full-name", "--verify", "--end-of-options", args.ref + ) + candidate = git( + "rev-parse", "--verify", "--end-of-options", f"{args.ref}^{{commit}}" + ) + release = ref.removeprefix("refs/tags/") if ref.startswith("refs/tags/") else None + stable, train, allows_breaks = train_policy(candidate, release) + baseline = f"refs/tags/{stable}" if release else candidate + if not release: + # merge-tree writes only Git objects, leaving HEAD and the worktree + # intact. Target-only additions are therefore not mistaken for deletions. + candidate = git("merge-tree", "--write-tree", baseline, "HEAD").splitlines()[0] + print( + f"Train: {train}; breaking changes {'allowed' if allows_breaks else 'forbidden'}" + ) + print(f"Comparing {candidate} against {baseline}", flush=True) + return compare(candidate, baseline, allows_breaks) + + +if __name__ == "__main__": + try: + sys.exit(main()) + except subprocess.CalledProcessError as error: + print(error.stderr or error.stdout or str(error), file=sys.stderr) + sys.exit(1) + except (OSError, ValueError, tarfile.TarError) as error: + print(error, file=sys.stderr) + sys.exit(1) diff --git a/tasks/scripts/generate-qualification-summary.sh b/tasks/scripts/generate-qualification-summary.sh index 1c5be1f20c..339e47ca80 100755 --- a/tasks/scripts/generate-qualification-summary.sh +++ b/tasks/scripts/generate-qualification-summary.sh @@ -26,9 +26,10 @@ current_profile_passed=true require_env \ RELEASE_TAG SOURCE_SHA IS_PRERELEASE GITHUB_RUN_ID GITHUB_RUN_ATTEMPT \ GITHUB_SERVER_URL GITHUB_REPOSITORY SECURITY_RESULT CONFORMANCE_RESULT \ - FEATURE_INTEGRATION_RESULT DOCKER_E2E_RESULT VM_E2E_RESULT + FEATURE_INTEGRATION_RESULT DOCKER_E2E_RESULT VM_E2E_RESULT PROTO_COMPATIBILITY_RESULT for result in \ + "${PROTO_COMPATIBILITY_RESULT}" \ "${SECURITY_RESULT}" \ "${CONFORMANCE_RESULT}" \ "${FEATURE_INTEGRATION_RESULT}" \ @@ -51,6 +52,7 @@ jq -n \ --arg run_url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/attempts/${GITHUB_RUN_ATTEMPT}" \ --arg generated_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ --arg security "${SECURITY_RESULT}" \ + --arg protobuf_compatibility "${PROTO_COMPATIBILITY_RESULT}" \ --arg conformance "${CONFORMANCE_RESULT}" \ --arg feature_integration "${FEATURE_INTEGRATION_RESULT}" \ --arg docker_e2e "${DOCKER_E2E_RESULT}" \ @@ -65,7 +67,7 @@ jq -n \ rfc_0014_complete: false, missing_suites: [ "upgrade", - "breaking API change review", + "SDK/configuration compatibility and migration review", "remaining RFC conformance configurations" ] }, @@ -77,6 +79,7 @@ jq -n \ }, generated_at: $generated_at, suites: { + protobuf_compatibility: $protobuf_compatibility, security: $security, conformance_integration: $conformance, feature_integration: $feature_integration, diff --git a/tasks/scripts/test-qualification-summary.sh b/tasks/scripts/test-qualification-summary.sh index a2272d11ee..bda87ac8db 100755 --- a/tasks/scripts/test-qualification-summary.sh +++ b/tasks/scripts/test-qualification-summary.sh @@ -22,6 +22,7 @@ export CONFORMANCE_RESULT=success export FEATURE_INTEGRATION_RESULT=success export DOCKER_E2E_RESULT=success export VM_E2E_RESULT=success +export PROTO_COMPATIBILITY_RESULT=success current_profile_passed=$("${GENERATOR}" "${TEST_DIR}/qualification-summary.json") [[ "${current_profile_passed}" == "false" ]] From b3e92013163ade4856d96f5857f5969d9a7bb4fc Mon Sep 17 00:00:00 2001 From: bornav <51048565+bornav@users.noreply.github.com> Date: Thu, 1 Oct 2026 14:23:04 +0000 Subject: [PATCH 21/33] feat(flake): add packages required to run mise command allowing us to compile vm driver from source (#2151) --- flake.nix | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/flake.nix b/flake.nix index 0635ff84bc..747a5f3c80 100644 --- a/flake.nix +++ b/flake.nix @@ -60,6 +60,13 @@ pkg-config # Coverage. lcov + # mise dependencies + mise + cmakeMinimal + zlib + openssl_3_5 + xz + gh kubernetes-helm syft trivy From 82e889374fa26246ca6efe7da8b9bc3ec0a07f4d Mon Sep 17 00:00:00 2001 From: Evan Lezar Date: Thu, 1 Oct 2026 14:29:09 +0000 Subject: [PATCH 22/33] test(tmachine): add Fedora RPM package installer (#4025) Signed-off-by: Evan Lezar --- .agents/skills/test-release-canary/SKILL.md | 7 + .github/workflows/branch-e2e.yml | 34 ++++- .../workflows/prepare-integration-inputs.yml | 36 +++++ .github/workflows/release-dev.yml | 10 +- .github/workflows/release-tag.yml | 10 +- CI.md | 5 + tests/ansible/playbooks/openshell-deb.yaml | 92 +------------ tests/ansible/playbooks/openshell-rpm.yaml | 34 +++++ .../tasks/main.yaml | 123 ++++++++++++++++++ .../templates/gateway.toml.j2 | 8 ++ .../tmachine_user_manager/tasks/main.yaml | 8 +- tests/config.nix | 11 ++ 12 files changed, 277 insertions(+), 101 deletions(-) create mode 100644 tests/ansible/playbooks/openshell-rpm.yaml create mode 100644 tests/ansible/roles/openshell_packaged_gateway/tasks/main.yaml create mode 100644 tests/ansible/roles/openshell_packaged_gateway/templates/gateway.toml.j2 diff --git a/.agents/skills/test-release-canary/SKILL.md b/.agents/skills/test-release-canary/SKILL.md index 4b95b805f8..15430d8610 100644 --- a/.agents/skills/test-release-canary/SKILL.md +++ b/.agents/skills/test-release-canary/SKILL.md @@ -32,6 +32,13 @@ before installing a snap. The Debian and Kubernetes CLI lanes remove snapd so they continue to exercise the dev Debian package. Kubernetes pins the matching `0.0.0-dev` chart and `:dev` images. +RPM package installation also has tmachine conformance coverage in +`Branch E2E Checks` (with `test:e2e`), `Release Dev`, and `Release Tag`. Those +lanes use the `rpm` installer on `fedora-podman-rootful` and +`fedora-podman-rootless` with candidate CLI and +gateway RPMs and matching runtime images. Branch RPM package builds run on +every approved branch run, independently of optional E2E labels. + The host-package jobs exercise fresh installs, not upgrades from a persisted schema-v1 gateway config. Validate Homebrew and RPM exact-default migration with the release-tooling and package lifecycle tests before relying on the canary. diff --git a/.github/workflows/branch-e2e.yml b/.github/workflows/branch-e2e.yml index 49cf199a3c..02b3508e76 100644 --- a/.github/workflows/branch-e2e.yml +++ b/.github/workflows/branch-e2e.yml @@ -82,13 +82,15 @@ jobs: version: needs: [pr_metadata] - if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_any_e2e == 'true' + if: needs.pr_metadata.outputs.should_run == 'true' permissions: contents: read runs-on: ubuntu-latest timeout-minutes: 5 outputs: cargo: ${{ steps.version.outputs.cargo }} + rpm_version: ${{ steps.version.outputs.rpm_version }} + rpm_release: ${{ steps.version.outputs.rpm_release }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -99,7 +101,13 @@ jobs: id: version run: | cargo="$(python3 tasks/scripts/release.py get-version --cargo)" - echo "cargo=$cargo" >> "$GITHUB_OUTPUT" + rpm_version="$(python3 tasks/scripts/release.py get-version --dev --rpm-version)" + rpm_release="$(python3 tasks/scripts/release.py get-version --dev --rpm-release)" + { + echo "cargo=$cargo" + echo "rpm_version=$rpm_version" + echo "rpm_release=$rpm_release" + } >> "$GITHUB_OUTPUT" build-binaries: needs: version @@ -200,14 +208,30 @@ jobs: packages: write uses: ./.github/workflows/build-images.yml + build-rpm: + name: Build RPM packages + needs: [pr_metadata, version, build-binaries] + if: needs.pr_metadata.outputs.should_run == 'true' + permissions: + actions: read + contents: read + uses: ./.github/workflows/rpm-package.yml + with: + checkout-ref: ${{ github.sha }} + rpm-version: ${{ needs.version.outputs.rpm_version }} + rpm-release: ${{ needs.version.outputs.rpm_release }} + cargo-version: ${{ needs.version.outputs.cargo }} + prepare-integration: - needs: [pr_metadata, build-binaries, build-images] + needs: [pr_metadata, build-binaries, build-images, build-rpm] if: needs.pr_metadata.outputs.run_integration == 'true' permissions: actions: read contents: read packages: read uses: ./.github/workflows/prepare-integration-inputs.yml + with: + rpm-artifact-name: rpm-linux-x86_64 # Run driver-independent conformance tests. conformance-integration: @@ -225,8 +249,8 @@ jobs: [ {"environment":"ubuntu-docker-rootful","installer":"binaries","testsuite":"conformance"}, {"environment":"ubuntu-k3s","installer":"k3s","testsuite":"conformance"}, - {"environment":"fedora-podman-rootful","installer":"binaries","testsuite":"conformance"}, - {"environment":"fedora-podman-rootless","installer":"binaries","testsuite":"conformance"} + {"environment":"fedora-podman-rootful","installer":"rpm","testsuite":"conformance"}, + {"environment":"fedora-podman-rootless","installer":"rpm","testsuite":"conformance"} ] # Run feature-specific integration tests: diff --git a/.github/workflows/prepare-integration-inputs.yml b/.github/workflows/prepare-integration-inputs.yml index d80e4943a1..d9b2c2a9f3 100644 --- a/.github/workflows/prepare-integration-inputs.yml +++ b/.github/workflows/prepare-integration-inputs.yml @@ -21,6 +21,11 @@ on: required: false type: string default: "" + rpm-artifact-name: + description: RPM package artifact to include in the tmachine inputs + required: false + type: string + default: "" outputs: source_sha: description: Source revision of the candidate artifacts @@ -94,6 +99,37 @@ jobs: mv artifacts/packages/download/*.deb artifacts/packages/openshell.deb rmdir artifacts/packages/download + - name: Download RPM package artifact + if: inputs['rpm-artifact-name'] != '' + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ inputs['rpm-artifact-name'] }} + path: artifacts/packages/rpm/download + github-token: ${{ github.token }} + run-id: ${{ inputs['artifact-run-id'] || github.run_id }} + + - name: Stage RPM package inputs + if: inputs['rpm-artifact-name'] != '' + run: | + set -euo pipefail + shopt -s nullglob + download_dir=artifacts/packages/rpm/download + cli_rpms=("$download_dir"/openshell-[0-9]*.rpm) + gateway_rpms=("$download_dir"/openshell-gateway-[0-9]*.rpm) + if [[ ${#cli_rpms[@]} -ne 1 || ${#gateway_rpms[@]} -ne 1 ]]; then + echo "candidate artifact must contain exactly one CLI and one gateway RPM" >&2 + exit 1 + fi + cli_identity=${cli_rpms[0]%.rpm} + gateway_identity=${gateway_rpms[0]%.rpm} + if [[ ${cli_identity##*.} != "${gateway_identity##*.}" ]]; then + echo "candidate CLI and gateway RPM architectures must match" >&2 + exit 1 + fi + mv "${cli_rpms[0]}" artifacts/packages/rpm/openshell.rpm + mv "${gateway_rpms[0]}" artifacts/packages/rpm/openshell-gateway.rpm + rm -rf "$download_dir" + - name: Log in to GHCR run: echo "${{ github.token }}" | docker login ghcr.io -u "${GITHUB_ACTOR}" --password-stdin diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml index 371144bf7c..80db20c942 100644 --- a/.github/workflows/release-dev.yml +++ b/.github/workflows/release-dev.yml @@ -102,7 +102,7 @@ jobs: checkout-ref: ${{ github.sha }} prepare-integration: - needs: [build-binaries, build-deb, build-images] + needs: [build-binaries, build-deb, build-images, build-rpm] permissions: actions: read contents: read @@ -110,6 +110,7 @@ jobs: uses: ./.github/workflows/prepare-integration-inputs.yml with: deb-artifact-name: deb-linux-amd64 + rpm-artifact-name: rpm-linux-x86_64 conformance-integration: needs: prepare-integration @@ -122,6 +123,13 @@ jobs: category: conformance source-sha: ${{ needs.prepare-integration.outputs.source_sha }} integration-inputs-artifact-id: ${{ needs.prepare-integration.outputs.integration_inputs_artifact_id }} + test-matrix: >- + [ + {"environment":"ubuntu-docker-rootful","installer":"deb","testsuite":"conformance"}, + {"environment":"ubuntu-k3s","installer":"k3s","testsuite":"conformance"}, + {"environment":"fedora-podman-rootful","installer":"rpm","testsuite":"conformance"}, + {"environment":"fedora-podman-rootless","installer":"rpm","testsuite":"conformance"} + ] feature-specific-integration: needs: prepare-integration diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 5ca1d33d98..b57e45708b 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -152,7 +152,7 @@ jobs: CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} prepare-integration: - needs: [compute-versions, build-binaries, build-deb, build-images] + needs: [compute-versions, build-binaries, build-deb, build-images, build-rpm] permissions: actions: read contents: read @@ -161,6 +161,7 @@ jobs: with: source-sha: ${{ needs.compute-versions.outputs.source_sha }} deb-artifact-name: deb-linux-amd64 + rpm-artifact-name: rpm-linux-x86_64 conformance-integration: needs: prepare-integration @@ -173,6 +174,13 @@ jobs: category: conformance source-sha: ${{ needs.prepare-integration.outputs.source_sha }} integration-inputs-artifact-id: ${{ needs.prepare-integration.outputs.integration_inputs_artifact_id }} + test-matrix: >- + [ + {"environment":"ubuntu-docker-rootful","installer":"deb","testsuite":"conformance"}, + {"environment":"ubuntu-k3s","installer":"k3s","testsuite":"conformance"}, + {"environment":"fedora-podman-rootful","installer":"rpm","testsuite":"conformance"}, + {"environment":"fedora-podman-rootless","installer":"rpm","testsuite":"conformance"} + ] feature-specific-integration: needs: prepare-integration diff --git a/CI.md b/CI.md index 33b9d748a6..e5a6b7add1 100644 --- a/CI.md +++ b/CI.md @@ -72,6 +72,11 @@ Windows checks are not required for merging and do not run in merge queues. Main and manual runs also build release binaries, with `continue-on-error: true` so Windows failures do not fail the workflow. +Every approved `Branch E2E Checks` run builds the RPM packages, including +runs without optional E2E labels. Core integration qualification installs the +CLI and gateway RPMs on Fedora with rootful and rootless Podman and runs conformance using +the matching runtime images. Release Dev and Release Tag run the same RPM lane. + Three opt-in labels enable the long-running E2E suites: - `test:e2e` runs the Docker, rootless Podman, Kubernetes, and VM E2E suites diff --git a/tests/ansible/playbooks/openshell-deb.yaml b/tests/ansible/playbooks/openshell-deb.yaml index f1589374d2..2304224cf0 100644 --- a/tests/ansible/playbooks/openshell-deb.yaml +++ b/tests/ansible/playbooks/openshell-deb.yaml @@ -21,94 +21,6 @@ ansible.builtin.apt: deb: /var/tmp/openshell.deb - - name: Copy OpenShell runtime images - become: true - ansible.builtin.copy: - src: "{{ item.src }}" - dest: "/var/tmp/{{ item.name }}.tar" - mode: "0644" - loop: - - name: openshell-sandbox - src: "{{ openshell_sandbox_image }}" - - name: openshell-supervisor - src: "{{ openshell_supervisor_image }}" - - - name: Load OpenShell runtime images - become: true - ansible.builtin.command: - argv: - - docker - - load - - --input - - "/var/tmp/{{ item }}.tar" - loop: - - openshell-sandbox - - openshell-supervisor - - # The package normally starts from its built-in runtime-image defaults. - # Qualification instead pins the candidate images staged by tmachine, so - # keep that override separate from the operator-owned gateway.toml. - - name: Create OpenShell qualification configuration directory - become: true - ansible.builtin.file: - path: /var/lib/openshell-qualification - state: directory - owner: root - group: root - mode: "0755" - - - name: Configure candidate OpenShell runtime images for qualification - become: true - ansible.builtin.copy: - dest: /var/lib/openshell-qualification/gateway.toml - owner: root - group: root - mode: "0644" - content: | - [openshell] - version = 2 - - [openshell.drivers.docker] - sandbox_runtime_image = "docker.io/openshell/sandbox:tmachine" - supervisor_image = "docker.io/openshell/supervisor:tmachine" - - - name: Create OpenShell environment directory - ansible.builtin.file: - path: /home/tmachine/.config/openshell - state: directory - mode: "0700" - - - name: Select qualification gateway configuration - ansible.builtin.copy: - dest: /home/tmachine/.config/openshell/gateway.env - mode: "0600" - content: | - OPENSHELL_GATEWAY_CONFIG=/var/lib/openshell-qualification/gateway.toml - - - name: Start tmachine user manager - ansible.builtin.include_role: - name: tmachine_user_manager - - - name: Start packaged OpenShell gateway service - ansible.builtin.systemd_service: - name: openshell-gateway.service - scope: user - daemon_reload: true - enabled: true - state: started - environment: - XDG_RUNTIME_DIR: /run/user/1000 - DBUS_SESSION_BUS_ADDRESS: unix:path=/run/user/1000/bus - - - name: Wait for OpenShell gateway - ansible.builtin.wait_for: - host: 127.0.0.1 - port: 17670 - timeout: 60 - - - name: Register packaged OpenShell gateway + - name: Prepare packaged OpenShell gateway ansible.builtin.include_role: - name: openshell_client - vars: - openshell_client_gateway_endpoint: https://127.0.0.1:17670 - openshell_client_gateway_name: openshell + name: openshell_packaged_gateway diff --git a/tests/ansible/playbooks/openshell-rpm.yaml b/tests/ansible/playbooks/openshell-rpm.yaml new file mode 100644 index 0000000000..e3c7934790 --- /dev/null +++ b/tests/ansible/playbooks/openshell-rpm.yaml @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +--- +- name: Install OpenShell RPM packages + hosts: all + gather_facts: false + tasks: + - name: Wait for SSH + ansible.builtin.wait_for_connection: + + - name: Copy OpenShell RPM packages + become: true + ansible.builtin.copy: + src: "{{ item.src }}" + dest: "/var/tmp/{{ item.name }}.rpm" + mode: "0644" + loop: + - { name: openshell, src: "{{ openshell_rpm }}" } + - { name: openshell-gateway, src: "{{ openshell_gateway_rpm }}" } + + - name: Install OpenShell RPM packages + become: true + ansible.builtin.dnf: + name: + - /var/tmp/openshell.rpm + - /var/tmp/openshell-gateway.rpm + state: present + disable_gpg_check: true + allow_downgrade: true + + - name: Prepare packaged OpenShell gateway + ansible.builtin.include_role: + name: openshell_packaged_gateway diff --git a/tests/ansible/roles/openshell_packaged_gateway/tasks/main.yaml b/tests/ansible/roles/openshell_packaged_gateway/tasks/main.yaml new file mode 100644 index 0000000000..a3e04f52bf --- /dev/null +++ b/tests/ansible/roles/openshell_packaged_gateway/tasks/main.yaml @@ -0,0 +1,123 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +--- +- name: Detect package gateway runtime + ansible.builtin.include_role: + name: openshell_gateway + tasks_from: detect.yaml + +- name: Create runtime image staging directory + become: true + ansible.builtin.file: + path: /var/lib/openshell + state: directory + owner: "{{ openshell_gateway_user }}" + group: "{{ openshell_gateway_user }}" + mode: "0700" + +- name: Load candidate runtime images + ansible.builtin.include_role: + name: openshell_gateway + tasks_from: "{{ openshell_gateway_driver }}.yaml" + +# Package defaults select published images. Keep candidate image overrides +# separate from the operator-owned gateway.toml in both installers. +- name: Create qualification configuration directory + become: true + ansible.builtin.file: + path: /var/lib/openshell-qualification + state: directory + owner: root + group: root + mode: "0755" + +- name: Configure candidate runtime images for qualification + become: true + ansible.builtin.template: + src: gateway.toml.j2 + dest: /var/lib/openshell-qualification/gateway.toml + owner: root + group: root + mode: "0644" + +- name: Create packaged gateway environment directory + become: true + ansible.builtin.file: + path: "{{ openshell_gateway_home }}/.config/openshell" + state: directory + owner: "{{ openshell_gateway_user }}" + group: "{{ openshell_gateway_user }}" + mode: "0700" + +- name: Select qualification gateway configuration + become: true + ansible.builtin.copy: + dest: "{{ openshell_gateway_home }}/.config/openshell/gateway.env" + owner: "{{ openshell_gateway_user }}" + group: "{{ openshell_gateway_user }}" + mode: "0600" + content: | + OPENSHELL_GATEWAY_CONFIG=/var/lib/openshell-qualification/gateway.toml + +- name: Start gateway user manager + ansible.builtin.include_role: + name: tmachine_user_manager + vars: + tmachine_user_manager_user: "{{ openshell_gateway_user }}" + tmachine_user_manager_uid: "{{ openshell_gateway_uid }}" + +- name: Start packaged gateway service + become: true + become_user: "{{ openshell_gateway_user }}" + ansible.builtin.systemd_service: + name: openshell-gateway.service + scope: user + daemon_reload: true + enabled: true + state: started + environment: + HOME: "{{ openshell_gateway_home }}" + XDG_RUNTIME_DIR: "/run/user/{{ openshell_gateway_uid }}" + DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ openshell_gateway_uid }}/bus" + +- name: Wait for packaged gateway + ansible.builtin.wait_for: + host: 127.0.0.1 + port: 17670 + timeout: 60 + +# The rootful Podman gateway uses root's image store and packaged user service. +# Copy only client credentials so the test runner can register that gateway +# without granting access to root's home or copying server signing keys. +- name: Prepare test client for rootful gateway + when: openshell_gateway_user == 'root' + block: + - name: Read rootful gateway client credentials + become: true + ansible.builtin.slurp: + src: "/root/.local/state/openshell/tls/{{ item }}" + loop: [ca.crt, client/tls.crt, client/tls.key] + register: openshell_packaged_gateway_client_credentials + no_log: true + + - name: Create test client credential directory + ansible.builtin.file: + path: /home/tmachine/.config/openshell/gateways/openshell/mtls + state: directory + mode: "0700" + + - name: Install test client credentials + ansible.builtin.copy: + content: "{{ item.content | b64decode }}" + dest: "/home/tmachine/.config/openshell/gateways/openshell/mtls/{{ item.item | basename }}" + mode: "0600" + loop: "{{ openshell_packaged_gateway_client_credentials.results }}" + no_log: true + +- name: Register packaged gateway + ansible.builtin.include_role: + name: openshell_client + vars: + openshell_client_gateway_endpoint: https://127.0.0.1:17670 + openshell_client_gateway_name: openshell diff --git a/tests/ansible/roles/openshell_packaged_gateway/templates/gateway.toml.j2 b/tests/ansible/roles/openshell_packaged_gateway/templates/gateway.toml.j2 new file mode 100644 index 0000000000..cb6d524228 --- /dev/null +++ b/tests/ansible/roles/openshell_packaged_gateway/templates/gateway.toml.j2 @@ -0,0 +1,8 @@ +{# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. #} +{# SPDX-License-Identifier: Apache-2.0 #} +[openshell] +version = 2 + +[openshell.drivers.{{ openshell_gateway_driver }}] +sandbox_runtime_image = "docker.io/openshell/sandbox:tmachine" +supervisor_image = "docker.io/openshell/supervisor:tmachine" diff --git a/tests/ansible/roles/tmachine_user_manager/tasks/main.yaml b/tests/ansible/roles/tmachine_user_manager/tasks/main.yaml index 1b2f2232af..5758331123 100644 --- a/tests/ansible/roles/tmachine_user_manager/tasks/main.yaml +++ b/tests/ansible/roles/tmachine_user_manager/tasks/main.yaml @@ -2,17 +2,17 @@ # SPDX-License-Identifier: Apache-2.0 --- -- name: Enable lingering for tmachine +- name: Enable lingering for gateway user become: true ansible.builtin.command: argv: - loginctl - enable-linger - - tmachine + - "{{ tmachine_user_manager_user | default('tmachine') }}" changed_when: false -- name: Start tmachine user manager +- name: Start gateway user manager become: true ansible.builtin.systemd_service: - name: user@1000.service + name: "user@{{ tmachine_user_manager_uid | default('1000') }}.service" state: started diff --git a/tests/config.nix b/tests/config.nix index 4bf7488ff8..26d27b712b 100644 --- a/tests/config.nix +++ b/tests/config.nix @@ -126,6 +126,17 @@ let openshell_sandbox_image = "../artifacts/images/openshell-sandbox-tmachine.tar"; }; } + { + name = "rpm"; + use_galaxy = false; + playbooks = [ "ansible/playbooks/openshell-rpm.yaml" ]; + inputs = { + openshell_rpm = "../artifacts/packages/rpm/openshell.rpm"; + openshell_gateway_rpm = "../artifacts/packages/rpm/openshell-gateway.rpm"; + openshell_supervisor_image = "../artifacts/images/openshell-supervisor-tmachine.tar"; + openshell_sandbox_image = "../artifacts/images/openshell-sandbox-tmachine.tar"; + }; + } ]; testsuites = [ From 35b92b5cc52bb18012537d0c45b60096f982e280 Mon Sep 17 00:00:00 2001 From: Bobbins228 Date: Thu, 1 Oct 2026 15:07:35 +0100 Subject: [PATCH 23/33] CARRY: feat(odh): add Konflux e2e testing image Build the odh-openshell-e2e UBI9 test image containing the OpenShell CLI, a compiled nextest archive, and tools for OpenShift. Prefetch both Cargo lockfiles, RPMs, and pinned cluster tools through Hermeto for the network-isolated Konflux build. Leave Tekton YAML generation to Konflux automation. Add local build and smoke checks, deployment scripts, tier selection, and JUnit and HTML reports. Serialize lifecycle and SELinux tests across nextest processes, restore lifecycle coverage, and fail empty tiers. Forward termination to nextest and clean up failed deployments while preserving local gateway registration on validation failures. Require namespace ownership before replacement. Remove the privileged SCC grant, use test-runner image metadata, and document the build, onboarding, and runtime flow under deploy/konflux/e2e-odh. Signed-off-by: Bobbins228 --- .config/nextest.toml | 37 + .dockerignore | 2 + deploy/docker/Dockerfile.konflux.e2e-odh | 134 ++ deploy/docker/Dockerfile.konflux.openclaw | 2 +- deploy/konflux/build-local.sh | 50 +- deploy/konflux/e2e-odh/README.md | 24 + deploy/konflux/e2e-odh/generic-fetcher.yaml | 34 + deploy/konflux/e2e-odh/rpms.in.yaml | 36 + deploy/konflux/e2e-odh/rpms.lock.yaml | 2030 +++++++++++++++++++ e2e/rust/tests/odh/AGENTS.md | 31 +- e2e/rust/tests/odh/README.md | 192 +- e2e/rust/tests/odh/run-odh-test-tier.sh | 169 +- e2e/rust/tests/odh/tiers.toml | 30 +- odh/scripts/e2e-odh-entrypoint.sh | 93 + odh/scripts/openshell-deploy-from-quay.sh | 301 +++ skills/debug-openshell-cluster/SKILL.md | 2 +- tasks/test-odh.toml | 4 +- 17 files changed, 3035 insertions(+), 136 deletions(-) create mode 100644 deploy/docker/Dockerfile.konflux.e2e-odh create mode 100644 deploy/konflux/e2e-odh/README.md create mode 100644 deploy/konflux/e2e-odh/generic-fetcher.yaml create mode 100644 deploy/konflux/e2e-odh/rpms.in.yaml create mode 100644 deploy/konflux/e2e-odh/rpms.lock.yaml create mode 100755 odh/scripts/e2e-odh-entrypoint.sh create mode 100755 odh/scripts/openshell-deploy-from-quay.sh diff --git a/.config/nextest.toml b/.config/nextest.toml index 19b4c21769..3c3047a2d7 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -22,6 +22,7 @@ slow-timeout = { period = "60s", terminate-after = 5 } # names because binary() fails in workspaces without the HA test binary. [test-groups] kubernetes-ha = { max-threads = 1 } +odh-gateway-lifecycle = { max-threads = 1 } [[profile.e2e-kubernetes.overrides]] filter = "test(/gateway_(scale_and_rollout|pod_rolls)$/)" @@ -30,3 +31,39 @@ test-group = "kubernetes-ha" # Relative to the profile store dir (`e2e/rust/target/nextest/e2e-kubernetes/`). [profile.e2e-kubernetes.junit] path = "../../../../../results/e2e-kubernetes.xml" + +[profile.e2e-odh] +fail-fast = false +status-level = "slow" +final-status-level = "fail" +failure-output = "immediate-final" +slow-timeout = { period = "60s", terminate-after = 5 } + +# Relative to the profile store dir (`e2e/rust/target/nextest/e2e-odh/`). +[profile.e2e-odh.junit] +path = "../../../../../results/e2e-odh.xml" + +# serial_test's in-process locks do not span nextest's test processes. +# Match names so this config also works without these test binaries. +[[profile.e2e-odh.overrides]] +filter = """ +test(=sandbox_exec_large_output_is_complete) +| test(=piped_exec_stdin_crosses_grpc_message_limit) +| test(=sandbox_stop_start_preserves_workspace) +| test(=sandbox_can_be_deleted_while_stopped) +| test(=canonical_main_exit_zero_completes_persistent_sandbox) +| test(=canonical_main_nonzero_exit_preserves_status) +| test(=detached_canonical_main_exit_zero_reaches_completed) +| test(=detached_canonical_main_nonzero_exit_reaches_error) +| test(=canonical_main_and_exec_receive_declared_environment) +| test(=detached_main_exit_during_provisioning_is_classified_as_workload_result) +| test(=canonical_tty_main_uses_sandbox_environment) +| test(=canonical_main_disconnect_reconnect_replays_history_for_same_process) +| test(=canonical_main_connect_recovers_its_ssh_transport) +| test(=canonical_main_connect_forwards_pid_targeted_termination_and_reaps_ssh) +| test(=canonical_main_exit_255_is_not_retried_as_transport_failure) +| test(=on_failure_policy_replaces_runtime_and_preserves_workspace) +| test(=sandbox_create_with_no_keep_preserves_failure_then_cleans_up) +| test(~tier1::selinux::) +""" +test-group = "odh-gateway-lifecycle" diff --git a/.dockerignore b/.dockerignore index 192b57b6ce..95c1e867a4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,6 +4,8 @@ # Rust build artifacts (use BuildKit cache mounts instead) target +**/target +**/node_modules # Python __pycache__ diff --git a/deploy/docker/Dockerfile.konflux.e2e-odh b/deploy/docker/Dockerfile.konflux.e2e-odh new file mode 100644 index 0000000000..294d3aa9a6 --- /dev/null +++ b/deploy/docker/Dockerfile.konflux.e2e-odh @@ -0,0 +1,134 @@ +# syntax=docker/dockerfile:1.4 + +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# ODH Shift-Left e2e runner. Hermeto prefetches both Cargo lockfiles, RPMs, +# and generic artifacts before this build runs without network access. +# No RUN heredocs and no RUN --mount: build-local.sh injects the Hermeto +# environment with awk. Every RUN must start at column 0 with `RUN ` and +# use `&& \` continuations. + +ARG RUST_VERSION=1.95.0 +ARG OC_VERSION=4.20.0 +ARG HELM_VERSION=4.2.0 +ARG NEXTEST_VERSION=0.9.143 + +FROM registry.access.redhat.com/ubi9/ubi:9.8@sha256:25a147defd01e19674714f55d17538c8dbe55d8c305fa157ecc3f9c8977b05b6 AS builder + +ARG RUST_VERSION +ARG NEXTEST_VERSION +ARG TARGETARCH +ARG OPENSHELL_VERSION="" + +RUN dnf install -y --nodocs --setopt=install_weak_deps=0 \ + gcc gcc-c++ make cmake openssl-devel git-core tar xz \ + && dnf clean all + +RUN RUST_ARCH=$(case "${TARGETARCH}" in amd64) echo x86_64;; arm64) echo aarch64;; *) exit 1;; esac) && \ + RUST_TRIPLE="${RUST_ARCH}-unknown-linux-gnu" && \ + mkdir -p /tmp/rust-toolchain && \ + tar xJf "/cachi2/output/deps/generic/rust-${RUST_VERSION}-${RUST_TRIPLE}.tar.xz" \ + -C /tmp/rust-toolchain --strip-components=1 && \ + /tmp/rust-toolchain/install.sh --prefix=/usr/local && \ + tar xzf "/cachi2/output/deps/generic/cargo-nextest-${NEXTEST_VERSION}-${RUST_TRIPLE}.tar.gz" \ + -C /usr/local/bin cargo-nextest + +ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse + +WORKDIR /home/odh/openshell-e2e-odh +COPY Cargo.toml Cargo.lock ./ +COPY .cargo/ .cargo/ +COPY .config/ .config/ +COPY crates/ crates/ +COPY proto/ proto/ +COPY providers/ providers/ +COPY examples/ examples/ +COPY e2e/ e2e/ +COPY scripts/ scripts/ +COPY tasks/ tasks/ +COPY odh/ odh/ +COPY deploy/helm/openshell/ deploy/helm/openshell/ +COPY deploy/helm/openshell-workspace/ deploy/helm/openshell-workspace/ + +RUN VER="${OPENSHELL_VERSION#v}" && \ + if [ -n "${VER}" ]; then export OPENSHELL_GIT_VERSION="${VER}"; fi && \ + cargo build --release --package openshell-cli && \ + mkdir -p /opt/openshell && \ + cargo nextest archive \ + --manifest-path e2e/rust/Cargo.toml \ + --target-dir e2e/rust/target \ + --features e2e-odh \ + --archive-file /opt/openshell/e2e-odh.tar.zst + +FROM registry.access.redhat.com/ubi9/ubi:9.8@sha256:25a147defd01e19674714f55d17538c8dbe55d8c305fa157ecc3f9c8977b05b6 + +ARG OC_VERSION +ARG HELM_VERSION +ARG TARGETARCH + +RUN dnf install -y --nodocs --setopt=install_weak_deps=0 \ + ca-certificates crypto-policies-scripts \ + python3.11 libxslt util-linux openssh-clients git-core tar \ + && dnf clean all && \ + update-crypto-policies --set DEFAULT:PQ + +COPY --from=builder --chmod=0555 \ + /usr/local/bin/cargo /usr/local/bin/cargo-nextest /usr/local/bin/ +COPY --from=builder --chmod=0555 \ + /home/odh/openshell-e2e-odh/target/release/openshell /usr/local/bin/openshell +COPY --from=builder /opt/openshell/e2e-odh.tar.zst /opt/openshell/e2e-odh.tar.zst + +RUN OC_ARCH=$(case "${TARGETARCH}" in amd64) echo amd64;; arm64) echo arm64;; *) exit 1;; esac) && \ + mkdir -p /tmp/oc /tmp/helm && \ + tar xzf "/cachi2/output/deps/generic/openshift-client-linux-${OC_ARCH}-rhel9-${OC_VERSION}.tar.gz" \ + -C /tmp/oc oc kubectl && \ + install -m 0555 /tmp/oc/oc /tmp/oc/kubectl /usr/local/bin/ && \ + tar xzf "/cachi2/output/deps/generic/helm-v${HELM_VERSION}-linux-${OC_ARCH}.tar.gz" \ + -C /tmp/helm && \ + install -m 0555 "/tmp/helm/linux-${OC_ARCH}/helm" /usr/local/bin/helm + +WORKDIR /home/odh/openshell-e2e-odh +COPY --chown=1000:1000 Cargo.toml Cargo.lock ./ +COPY --chown=1000:1000 .cargo/ .cargo/ +COPY --chown=1000:1000 .config/ .config/ +COPY --chown=1000:1000 crates/ crates/ +COPY --chown=1000:1000 proto/ proto/ +COPY --chown=1000:1000 providers/ providers/ +COPY --chown=1000:1000 examples/ examples/ +COPY --chown=1000:1000 e2e/ e2e/ +COPY --chown=1000:1000 scripts/ scripts/ +COPY --chown=1000:1000 tasks/ tasks/ +COPY --chown=1000:1000 odh/ odh/ +COPY --chown=1000:1000 deploy/helm/openshell/ deploy/helm/openshell/ +COPY --chown=1000:1000 deploy/helm/openshell-workspace/ deploy/helm/openshell-workspace/ + +RUN install -d -o 1000 -g 1000 results .kube /home/odh/.config/openshell && \ + chown 1000:1000 /home/odh /home/odh/openshell-e2e-odh + +# Fail the build if an expected test runtime tool is absent. In particular, +# sandbox connection uses ssh, and future repository tests need git. +RUN for tool in bash oc kubectl helm openshell cargo cargo-nextest \ + python3.11 xsltproc script ssh git tar base64; do \ + command -v "${tool}" >/dev/null || { echo "missing e2e runtime tool: ${tool}" >&2; exit 1; }; \ + done + +ENV HOME=/home/odh \ + KUBECONFIG=/home/odh/openshell-e2e-odh/.kube/config \ + OPENSHELL_BIN=/usr/local/bin/openshell \ + OPENSHELL_E2E_NEXTEST_ARCHIVE=/opt/openshell/e2e-odh.tar.zst \ + OPENSHELL_E2E_DEPLOY_GATEWAY=1 \ + PYTHON_BIN=python3.11 + +LABEL com.redhat.component="odh-openshell-e2e-container" \ + description="ODH Shift-Left e2e tests for OpenShell" \ + summary="ODH Shift-Left e2e tests for OpenShell" \ + name="opendatahub/odh-openshell-e2e" \ + maintainer="['managed-open-data-hub@redhat.com']" \ + io.openshift.expose-services="" \ + io.k8s.display-name="odh-openshell-e2e" \ + io.k8s.description="odh-openshell-e2e" + +USER 1000:1000 +ENTRYPOINT ["/home/odh/openshell-e2e-odh/odh/scripts/e2e-odh-entrypoint.sh"] +CMD ["smoke"] diff --git a/deploy/docker/Dockerfile.konflux.openclaw b/deploy/docker/Dockerfile.konflux.openclaw index 5f992f1756..72d1007c41 100644 --- a/deploy/docker/Dockerfile.konflux.openclaw +++ b/deploy/docker/Dockerfile.konflux.openclaw @@ -19,7 +19,7 @@ # # Local build: ./deploy/konflux/build-local.sh openclaw # -# No RUN heredocs and no RUN --mount: build-local.sh sed-injects +# No RUN heredocs and no RUN --mount: build-local.sh awk-injects # `. /cachi2/cachi2.env &&` after every `RUN ` to source the hermetic # environment, so every RUN must start at column 0 with `RUN ` and use # `&& \` continuations only. diff --git a/deploy/konflux/build-local.sh b/deploy/konflux/build-local.sh index 0126adb7f5..4ac0177542 100755 --- a/deploy/konflux/build-local.sh +++ b/deploy/konflux/build-local.sh @@ -1,9 +1,12 @@ #!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + # Build Konflux images locally using Hermeto prefetched dependencies. # Replicates the Konflux hermetic build pipeline (--network none). # # Prerequisites: -# - hermeto (pip install git+https://github.com/hermetoproject/hermeto.git) +# - hermeto and rpm (the RPM backend requires a Linux environment) # - podman # # Usage: @@ -11,6 +14,7 @@ # ./deploy/konflux/build-local.sh supervisor # ./deploy/konflux/build-local.sh sandbox # ./deploy/konflux/build-local.sh openclaw +# ./deploy/konflux/build-local.sh e2e-odh # ./deploy/konflux/build-local.sh all # # The openclaw smoke test additionally requires host openssl. @@ -33,21 +37,34 @@ PLATFORM="${PLATFORM:-${DEFAULT_PLATFORM}}" CLEANUP_PATHS=() RESTORE_FILES=() +CONFIG_BACKUP_DIR="$(mktemp -d)" +cp -p "${REPO_ROOT}/.cargo/config.toml" "${CONFIG_BACKUP_DIR}/root-config.toml" +if [[ -f "${REPO_ROOT}/e2e/rust/.cargo/config.toml" ]]; then + cp -p "${REPO_ROOT}/e2e/rust/.cargo/config.toml" "${CONFIG_BACKUP_DIR}/e2e-config.toml" +fi cleanup() { local i for ((i = 0; i < ${#RESTORE_FILES[@]}; i += 2)); do cp -p "${RESTORE_FILES[i]}" "${RESTORE_FILES[i+1]}" done + cp -p "${CONFIG_BACKUP_DIR}/root-config.toml" "${REPO_ROOT}/.cargo/config.toml" + if [[ -f "${CONFIG_BACKUP_DIR}/e2e-config.toml" ]]; then + cp -p "${CONFIG_BACKUP_DIR}/e2e-config.toml" "${REPO_ROOT}/e2e/rust/.cargo/config.toml" + else + rm -f "${REPO_ROOT}/e2e/rust/.cargo/config.toml" + rmdir "${REPO_ROOT}/e2e/rust/.cargo" 2>/dev/null || true + fi for p in "${CLEANUP_PATHS[@]}"; do rm -rf "$p" done - git -C "${REPO_ROOT}" checkout .cargo/config.toml 2>/dev/null || true + rm -rf "${CONFIG_BACKUP_DIR}" } trap cleanup EXIT build_image() { local component="$1" - local dockerfile konfig_dir output_dir repos_dir prefetch_input + local dockerfile konfig_dir output_dir repos_dir prefetch_input image_name + image_name="openshell-${component}-konflux" prefetch_input="" case "$component" in @@ -75,6 +92,17 @@ build_image() { {\"path\": \"${konfig_dir}\", \"type\": \"rpm\"} ]" ;; + e2e-odh) + image_name="odh-openshell-e2e" + dockerfile="deploy/docker/Dockerfile.konflux.e2e-odh" + konfig_dir="deploy/konflux/e2e-odh" + prefetch_input="[ + {\"path\": \".\", \"type\": \"cargo\"}, + {\"path\": \"e2e/rust\", \"type\": \"cargo\"}, + {\"path\": \"${konfig_dir}\", \"type\": \"rpm\"}, + {\"path\": \"${konfig_dir}\", \"type\": \"generic\", \"lockfile\": \"generic-fetcher.yaml\"} + ]" + ;; *) echo "Unknown component: $component" >&2 exit 1 @@ -132,7 +160,8 @@ build_image() { hermetic_dockerfile=$(mktemp) CLEANUP_PATHS+=("${hermetic_dockerfile}") cp "${REPO_ROOT}/${dockerfile}" "${hermetic_dockerfile}" - sed -i 's|^\s*RUN |RUN . /cachi2/cachi2.env \&\& \\\n |i' "${hermetic_dockerfile}" + awk '/^[[:space:]]*RUN / { match($0, /RUN /); $0 = substr($0, 1, RSTART - 1) "RUN . /cachi2/cachi2.env && " sprintf("%c", 92) "\n " substr($0, RSTART + RLENGTH) } { print }' "${hermetic_dockerfile}" > "${hermetic_dockerfile}.injected" + mv "${hermetic_dockerfile}.injected" "${hermetic_dockerfile}" # Disable subscription-manager so it doesn't inject RHEL repos that fail # DNS under --network=none. Same as Konflux Tekton script (unlink rhel secrets). @@ -158,17 +187,24 @@ build_image() { --volume "${sm_conf}:/etc/dnf/plugins/subscription-manager.conf:Z" \ --volume "${empty_secrets}:/run/secrets:Z" \ --network none \ - -t "openshell-${component}-konflux" \ + -t "${image_name}" \ "${REPO_ROOT}" echo "=== ${component} built successfully ===" # The sandbox runtime image is ubi-micro without crypto-policies. # openclaw stays on UBI 9 with DEFAULT:PQ; smoke-test.sh asserts it. if [[ "${component}" != "sandbox" && "${component}" != "openclaw" ]]; then - test "$(podman run --rm --user=0 --entrypoint /usr/bin/update-crypto-policies "openshell-${component}-konflux" --show)" = "DEFAULT:PQ" + test "$(podman run --rm --platform "${PLATFORM}" --user=0 --entrypoint /usr/bin/update-crypto-policies "${image_name}" --show)" = "DEFAULT:PQ" fi if [[ "${component}" == "openclaw" ]]; then PLATFORM="${PLATFORM}" bash "${REPO_ROOT}/deploy/konflux/openclaw/smoke-test.sh" "openshell-${component}-konflux" + elif [[ "${component}" == "e2e-odh" ]]; then + podman run --rm --platform "${PLATFORM}" --entrypoint openshell "${image_name}" --version + podman run --rm --platform "${PLATFORM}" --entrypoint cargo-nextest "${image_name}" \ + nextest list --archive-file /opt/openshell/e2e-odh.tar.zst \ + --workspace-remap /home/odh/openshell-e2e-odh/e2e/rust \ + --config-file /home/odh/openshell-e2e-odh/.config/nextest.toml \ + --profile e2e-odh --message-format json > /dev/null else podman run --rm --platform "${PLATFORM}" "openshell-${component}-konflux" --help 2>&1 | head -3 fi @@ -176,7 +212,7 @@ build_image() { } if [[ $# -eq 0 ]]; then - echo "Usage: $0 {gateway|supervisor|sandbox|cli|openclaw|all}" >&2 + echo "Usage: $0 {gateway|supervisor|sandbox|cli|openclaw|e2e-odh|all}" >&2 exit 1 fi diff --git a/deploy/konflux/e2e-odh/README.md b/deploy/konflux/e2e-odh/README.md new file mode 100644 index 0000000000..249abe44df --- /dev/null +++ b/deploy/konflux/e2e-odh/README.md @@ -0,0 +1,24 @@ +# ODH E2E Image Build + +The downstream ODH Konflux e2e image is a separate test artifact. Its +multi-stage UBI9 build compiles the CLI and a nextest archive from two +independent Cargo lockfiles, with Rust, RPMs, and cluster tools prefetched by +Hermeto for a network-isolated build. The runtime image carries the compiled +archive, Cargo and nextest executables, cluster tools, the SSH client needed +by sandbox lifecycle tests, and Git for repository workloads. + +At runtime, the image entrypoint uses the Quay deployment script to create a +gateway on the target OpenShift cluster, runs one selected test tier, writes +JUnit and HTML reports to a mounted results directory, and tears down the +deployment. Cleanup is armed before deployment and tracks namespace creation +separately from local gateway registration, so rollout failures remove the +owned namespace and validation failures preserve local registration. Signals +are forwarded through the tier runner to nextest before teardown. Extracted +client mTLS material is stored in a mode `0700` directory with mode `0600` +files. The Rust tests consume the deployed gateway. + +Konflux onboarding must use the `linux-m2xlarge/*` builders and retain both +Cargo prefetch inputs (`.` and `e2e/rust`). The image name is +`odh-openshell-e2e`; Konflux automation generates the Tekton YAML files. +Before merging, validate the local hermetic build on both `linux/amd64` and +`linux/arm64` using `PLATFORM` with `deploy/konflux/build-local.sh e2e-odh`. diff --git a/deploy/konflux/e2e-odh/generic-fetcher.yaml b/deploy/konflux/e2e-odh/generic-fetcher.yaml new file mode 100644 index 0000000000..aed78447ce --- /dev/null +++ b/deploy/konflux/e2e-odh/generic-fetcher.yaml @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Hermeto places these pinned artifacts under /cachi2/output/deps/generic/. +metadata: + version: "1.0" +artifacts: + - download_url: https://static.rust-lang.org/dist/rust-1.95.0-x86_64-unknown-linux-gnu.tar.xz + checksum: "sha256:2e0338f18ecbaa4a0f631b9e80e8b8e26bb6fe77dd5454fba8a70cf96c1e84a1" + filename: rust-1.95.0-x86_64-unknown-linux-gnu.tar.xz + - download_url: https://static.rust-lang.org/dist/rust-1.95.0-aarch64-unknown-linux-gnu.tar.xz + checksum: "sha256:094c9c36531911c5cc7dd6ab2d3069ab8dcd744d6239b0bda1387b243dfc391e" + filename: rust-1.95.0-aarch64-unknown-linux-gnu.tar.xz + + - download_url: https://mirror.openshift.com/pub/openshift-v4/clients/ocp/4.20.0/openshift-client-linux-amd64-rhel9-4.20.0.tar.gz + checksum: "sha256:dbc11aa32c8fa967c47c46900c524798b782b355615c9ca8ec2bbba647850420" + filename: openshift-client-linux-amd64-rhel9-4.20.0.tar.gz + - download_url: https://mirror.openshift.com/pub/openshift-v4/clients/ocp/4.20.0/openshift-client-linux-arm64-rhel9-4.20.0.tar.gz + checksum: "sha256:f8a18b7f6a3498d71825d954a54f7b0f9e042d9a7887d9e88c4a44371dddd5e4" + filename: openshift-client-linux-arm64-rhel9-4.20.0.tar.gz + + - download_url: https://get.helm.sh/helm-v4.2.0-linux-amd64.tar.gz + checksum: "sha256:97dbeb971be4ac4b27e3839976d9564c0fb35c6f3b1da89dd1e292d236af4096" + filename: helm-v4.2.0-linux-amd64.tar.gz + - download_url: https://get.helm.sh/helm-v4.2.0-linux-arm64.tar.gz + checksum: "sha256:1f8de130dfbd04de64978e7b852a7a547be1404956a366608276d2520b678670" + filename: helm-v4.2.0-linux-arm64.tar.gz + + - download_url: https://github.com/nextest-rs/nextest/releases/download/cargo-nextest-0.9.143/cargo-nextest-0.9.143-x86_64-unknown-linux-gnu.tar.gz + checksum: "sha256:66786b9abe23920d022a182d1416b1bbc8130dd4872a9553d76985a1708dcd1e" + filename: cargo-nextest-0.9.143-x86_64-unknown-linux-gnu.tar.gz + - download_url: https://github.com/nextest-rs/nextest/releases/download/cargo-nextest-0.9.143/cargo-nextest-0.9.143-aarch64-unknown-linux-gnu.tar.gz + checksum: "sha256:2a64b3566a92508550a7ab29c3e8db25472ca37730ecb4d22100b6aa440c2a68" + filename: cargo-nextest-0.9.143-aarch64-unknown-linux-gnu.tar.gz diff --git a/deploy/konflux/e2e-odh/rpms.in.yaml b/deploy/konflux/e2e-odh/rpms.in.yaml new file mode 100644 index 0000000000..1832f466f2 --- /dev/null +++ b/deploy/konflux/e2e-odh/rpms.in.yaml @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Build and runtime RPM closure for Dockerfile.konflux.e2e-odh. +# Regenerate with: +# rpm-lockfile-prototype --outfile deploy/konflux/e2e-odh/rpms.lock.yaml \ +# deploy/konflux/e2e-odh/rpms.in.yaml +contentOrigin: + repos: + - repoid: ubi-9-baseos-rpms + baseurl: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/$basearch/baseos/os/ + - repoid: ubi-9-appstream-rpms + baseurl: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/$basearch/appstream/os/ +packages: + # Build stage (git-core is also installed in the runtime stage) + - gcc + - gcc-c++ + - make + - cmake + - openssl-devel + - git-core + - tar + - xz + # Runtime stage + - ca-certificates + - crypto-policies-scripts + - python3.11 + - libxslt + - util-linux + - openssh-clients +arches: + - x86_64 + - aarch64 +installWeakDeps: false +context: + bare: true diff --git a/deploy/konflux/e2e-odh/rpms.lock.yaml b/deploy/konflux/e2e-odh/rpms.lock.yaml new file mode 100644 index 0000000000..f604e6202d --- /dev/null +++ b/deploy/konflux/e2e-odh/rpms.lock.yaml @@ -0,0 +1,2030 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +--- +lockfileVersion: 1 +lockfileVendor: redhat +arches: +- arch: aarch64 + packages: + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cmake-3.31.8-3.el9.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 11655593 + checksum: sha256:2a77fe1c3784083dcdebdd548c16d823b3e4a5adb8d6c00e36841aa633eab53b + name: cmake + evr: 3.31.8-3.el9 + sourcerpm: cmake-3.31.8-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cmake-data-3.31.8-3.el9.noarch.rpm + repoid: ubi-9-appstream-rpms + size: 2829291 + checksum: sha256:1cdc2e88a996c575b750483c8f562674e4b50a6ab414c7bbe6f6b641c1db7bd9 + name: cmake-data + evr: 3.31.8-3.el9 + sourcerpm: cmake-3.31.8-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cmake-filesystem-3.31.8-3.el9.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 19282 + checksum: sha256:69a498723354740357fc3f4b4cd235da38fadd98835da193bec0c0850f68f3ad + name: cmake-filesystem + evr: 3.31.8-3.el9 + sourcerpm: cmake-3.31.8-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/c/cpp-11.5.0-14.el9.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 10798392 + checksum: sha256:eb16ef369b981c0dca6149e971a63f74ea09c09f1c638086e3cda1e0591ac21b + name: cpp + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/e/emacs-filesystem-27.2-18.el9.noarch.rpm + repoid: ubi-9-appstream-rpms + size: 9495 + checksum: sha256:49d7b88a05a72c15b78191a987e6def04fda8e2e4ff75711f715d0c0ecadc60f + name: emacs-filesystem + evr: 1:27.2-18.el9 + sourcerpm: emacs-27.2-18.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-11.5.0-14.el9.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 31291354 + checksum: sha256:542e635ac17b548cc03ab4347438d49f96837c1d91d12714b6b2764ec566156c + name: gcc + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/gcc-c++-11.5.0-14.el9.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 12994215 + checksum: sha256:aabe892798a2272d65c269fd6aa14d3b3dfc782c98f92f8fda30c2a4fa33bf6d + name: gcc-c++ + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/git-core-2.52.0-1.el9.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 5392428 + checksum: sha256:7082917982981bf611c089e7d9d53b63e969af61a37cfd65b5c4081d5b260a1c + name: git-core + evr: 2.52.0-1.el9 + sourcerpm: git-2.52.0-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/g/glibc-devel-2.34-275.el9_8.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 576947 + checksum: sha256:aef79b53955c2ec67efdc39cb91148b377f8719a8b68cf76c7ddcffbcb6b8bf0 + name: glibc-devel + evr: 2.34-275.el9_8 + sourcerpm: glibc-2.34-275.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.49.1.el9_8.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 2899913 + checksum: sha256:929179b11dddaa905f5261d8df9977ce70f311fb482d01ef13ff2da6d8c1e825 + name: kernel-headers + evr: 5.14.0-687.49.1.el9_8 + sourcerpm: kernel-5.14.0-687.49.1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libasan-11.5.0-14.el9.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 409047 + checksum: sha256:854a84e72d40c2a062d112b93f8a694044fd7dc5b3afe7a869a448a12844c3e6 + name: libasan + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 67120 + checksum: sha256:3763354a5f45d886f9976eec20eb34f8afc2144c69ffba07de546f2820893c70 + name: libmpc + evr: 1.2.1-4.el9 + sourcerpm: libmpc-1.2.1-4.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libnsl2-2.0.0-1.el9.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 32849 + checksum: sha256:5b38c8b55dbfc549271617e132d2d98ceaa9ca30711f73edd8b39a6af689de27 + name: libnsl2 + evr: 2.0.0-1.el9 + sourcerpm: libnsl2-2.0.0-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libstdc++-devel-11.5.0-14.el9.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 2520018 + checksum: sha256:5a2474c2e817b008212e5a94770d8bfbaad17e9ebba2a38d734907e594ac2aac + name: libstdc++-devel + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libtool-ltdl-2.4.6-46.el9.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 37581 + checksum: sha256:85f38e641398438f7f08526d7003f47e47a14369798464fd67c465134258e964 + name: libtool-ltdl + evr: 2.4.6-46.el9 + sourcerpm: libtool-2.4.6-46.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libubsan-11.5.0-14.el9.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 178996 + checksum: sha256:d0adfda8f1d8ddf05a46292228e31cf887d731e7999154603e148e3d70d1f182 + name: libubsan + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libuv-1.42.0-2.el9_4.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 150129 + checksum: sha256:4dc8a40da74e0f9823356460ee11f183c70f382953700fffef0c448198a677cc + name: libuv + evr: 1:1.42.0-2.el9_4 + sourcerpm: libuv-1.42.0-2.el9_4.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libxcrypt-devel-4.4.18-3.el9.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 33051 + checksum: sha256:9d621f33df35b9c274b8d65457d6c67fc1522b6c62cf7b2341a4a99f39a93507 + name: libxcrypt-devel + evr: 4.4.18-3.el9 + sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/l/libxslt-1.1.34-14.el9_8.1.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 251817 + checksum: sha256:c9c423fac638f5cb332c065c2a4d78b969a52e0d8f30b894494062ebf9e7e8aa + name: libxslt + evr: 1.1.34-14.el9_8.1 + sourcerpm: libxslt-1.1.34-14.el9_8.1.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/m/mpdecimal-2.5.1-3.el9.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 92062 + checksum: sha256:6bdb76d4bb510b0e435698a46a09d0849fb07b2f53c00239e8989d8f141d1d14 + name: mpdecimal + evr: 2.5.1-3.el9 + sourcerpm: mpdecimal-2.5.1-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/o/openssl-devel-3.5.8-1.el9_8.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 5039228 + checksum: sha256:49aec716fb44d8a07528f59a635d40a47a847b05e6cf57735994e9c2430efa12 + name: openssl-devel + evr: 1:3.5.8-1.el9_8 + sourcerpm: openssl-3.5.8-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/p/python3.11-3.11.13-10.el9_8.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 32079 + checksum: sha256:26c2d7d2c7a76e3bcb0bf8d2640beebd55ca31b1924ab6f4f4afdd50666c86a9 + name: python3.11 + evr: 3.11.13-10.el9_8 + sourcerpm: python3.11-3.11.13-10.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/p/python3.11-libs-3.11.13-10.el9_8.aarch64.rpm + repoid: ubi-9-appstream-rpms + size: 10690490 + checksum: sha256:4405c45a6fa7e992dd3db0719b1fadb5d38771f854ede80f9f416b3331a82c37 + name: python3.11-libs + evr: 3.11.13-10.el9_8 + sourcerpm: python3.11-3.11.13-10.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/p/python3.11-pip-wheel-22.3.1-6.el9.noarch.rpm + repoid: ubi-9-appstream-rpms + size: 1488665 + checksum: sha256:0e7e797af157c892a9fce0b6bf9f7b77db57250a16f049ae631e9acae79b5156 + name: python3.11-pip-wheel + evr: 22.3.1-6.el9 + sourcerpm: python3.11-pip-22.3.1-6.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/p/python3.11-setuptools-wheel-65.5.1-5.el9.noarch.rpm + repoid: ubi-9-appstream-rpms + size: 730338 + checksum: sha256:33ad44dff52114caa7b85f6e218ad5f9ccd88a538a3cf808377762ff01efb05f + name: python3.11-setuptools-wheel + evr: 65.5.1-5.el9 + sourcerpm: python3.11-setuptools-65.5.1-5.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/a/alternatives-1.24-2.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 42137 + checksum: sha256:6f7c0667ac015bc0d40836c9f55c73ebf65a209069f69aa8f58e6b4655c820a8 + name: alternatives + evr: 1.24-2.el9 + sourcerpm: chkconfig-1.24-2.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/a/audit-libs-3.1.5-8.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 128901 + checksum: sha256:11880ec70b575841843cbee0853e03e50a0506321ea0a6f76c0c8145d79ae531 + name: audit-libs + evr: 3.1.5-8.el9 + sourcerpm: audit-3.1.5-8.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/basesystem-11-13.el9.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 8229 + checksum: sha256:f498b0813fa1a825d550e8e3a9e42255eabfa18e6fc96adfc6cc8fa7e16dd513 + name: basesystem + evr: 11-13.el9 + sourcerpm: basesystem-11-13.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/bash-5.1.8-9.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 1760045 + checksum: sha256:7dc1febec9c2fb184ed4407f8a188ab267b7e46b3534866f702c6266008ababa + name: bash + evr: 5.1.8-9.el9 + sourcerpm: bash-5.1.8-9.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/binutils-2.35.2-72.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 5021411 + checksum: sha256:72e9fd9c4976413060df02e37d358fca208ab144d78e321f448a488d50967155 + name: binutils + evr: 2.35.2-72.el9 + sourcerpm: binutils-2.35.2-72.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/binutils-gold-2.35.2-72.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 908723 + checksum: sha256:269bb24ded2f23dcdb74c04597b13281ce6ac47a87a14831ee639d985323bd04 + name: binutils-gold + evr: 2.35.2-72.el9 + sourcerpm: binutils-2.35.2-72.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/b/bzip2-libs-1.0.8-11.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 47655 + checksum: sha256:8267a866b9289ac4e4a92cb4642adcdeff97c2ed816ddda87ed5d5e9d9431a2f + name: bzip2-libs + evr: 1.0.8-11.el9 + sourcerpm: bzip2-1.0.8-11.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/ca-certificates-2025.2.80_v9.0.305-91.el9.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 1072208 + checksum: sha256:0554bf65d573950e7d550a07bf7d0b3def85ca3b45a7fbde4cce7cadd9a476a7 + name: ca-certificates + evr: 2025.2.80_v9.0.305-91.el9 + sourcerpm: ca-certificates-2025.2.80_v9.0.305-91.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/coreutils-8.32-41.el9_8.1.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 1175876 + checksum: sha256:22827aedd764c1ab706086965859e7f8fef0d719ac0b3d8735f6b8e53b0a13e9 + name: coreutils + evr: 8.32-41.el9_8.1 + sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.1.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 2115646 + checksum: sha256:b449955249a6d2da7369522a5ac2759919d36633e129ba88924057814906d5f5 + name: coreutils-common + evr: 8.32-41.el9_8.1 + sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/cracklib-2.9.6-28.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 102026 + checksum: sha256:d85216b672a15e5dd8cc771b853e3b73e832034949ee23998d8403609fba00a2 + name: cracklib + evr: 2.9.6-28.el9 + sourcerpm: cracklib-2.9.6-28.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/cracklib-dicts-2.9.6-28.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 3829400 + checksum: sha256:807345f95c448cb58d4db8d059f76e4c139ef029887d59b431efd20db934745a + name: cracklib-dicts + evr: 2.9.6-28.el9 + sourcerpm: cracklib-2.9.6-28.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/crypto-policies-20260224-1.gitea0f072.el9_8.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 98707 + checksum: sha256:a9ad86b8df75a7c9c29c6f6dd4b8f78cfa1f42b492eff0d11b82c457d37a7f9f + name: crypto-policies + evr: 20260224-1.gitea0f072.el9_8 + sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/crypto-policies-scripts-20260224-1.gitea0f072.el9_8.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 111065 + checksum: sha256:cec44db94e9132008a7fe4e1aa764a29e9c125989c9ac2411f3cf6f8fb669dfd + name: crypto-policies-scripts + evr: 20260224-1.gitea0f072.el9_8 + sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/c/cyrus-sasl-lib-2.1.27-22.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 771760 + checksum: sha256:7e4f331fc477f0a8482c825ab1b6bfec7f4007481f4eb53fde7fa0ef2d1f6cde + name: cyrus-sasl-lib + evr: 2.1.27-22.el9 + sourcerpm: cyrus-sasl-2.1.27-22.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-debuginfod-client-0.194-1.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 42824 + checksum: sha256:bbcf05d0b46d42c85807d774788edc39528a6898bd880baf11fb77729f59272d + name: elfutils-debuginfod-client + evr: 0.194-1.el9 + sourcerpm: elfutils-0.194-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-default-yama-scope-0.194-1.el9.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 8949 + checksum: sha256:c473f42c0fec97e2830580110509e4522bc62d9b11761d062499354a0bf58959 + name: elfutils-default-yama-scope + evr: 0.194-1.el9 + sourcerpm: elfutils-0.194-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-libelf-0.194-1.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 203006 + checksum: sha256:04ff63b7b669b16827f3688baa0be4a2782f1405db3c6d3ee03c0e4cebc2cdf2 + name: elfutils-libelf + evr: 0.194-1.el9 + sourcerpm: elfutils-0.194-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/elfutils-libs-0.194-1.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 271645 + checksum: sha256:b90a6ad3e465995538785a2536986ad705625daf606d6258bf23d1dd29aec208 + name: elfutils-libs + evr: 0.194-1.el9 + sourcerpm: elfutils-0.194-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.3.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 122400 + checksum: sha256:df004398da989f75bae9267cfb0ad402ada6e6db46ebef0932c869894c013c08 + name: expat + evr: 2.5.0-6.el9_8.3 + sourcerpm: expat-2.5.0-6.el9_8.3.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/f/filesystem-3.16-5.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 5003914 + checksum: sha256:484bc41109c49066cf350344150abe144e63263e0fafa0bf12c5a47f853e6a49 + name: filesystem + evr: 3.16-5.el9 + sourcerpm: filesystem-3.16-5.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/f/findutils-4.8.0-7.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 564807 + checksum: sha256:158af4d5ecbd8b87f0da762ea1655bd4c86512071a95d8307eda3e0b3991105d + name: findutils + evr: 1:4.8.0-7.el9 + sourcerpm: findutils-4.8.0-7.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gawk-5.1.0-6.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 1024204 + checksum: sha256:a4b7202ac90653a7d3e072c2444bde6a9270d6a818eb6f2ffcfcaa50774f1fad + name: gawk + evr: 5.1.0-6.el9 + sourcerpm: gawk-5.1.0-6.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 60311 + checksum: sha256:74fffe15dd7f5a41c7d1990c2804defa1b45fb845da29465b73a81d5866e8a72 + name: gdbm-libs + evr: 1:1.23-1.el9 + sourcerpm: gdbm-1.23-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-2.34-275.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 1813928 + checksum: sha256:e40a78100f731b5f5a1b235880a0aaad5b08bb32e6521e90535b7f4a94c6e9e9 + name: glibc + evr: 2.34-275.el9_8 + sourcerpm: glibc-2.34-275.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-common-2.34-275.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 312665 + checksum: sha256:24e1c7189d101531c526dd3cc1a42ab62d89f874824b54fb6b518ec24f190554 + name: glibc-common + evr: 2.34-275.el9_8 + sourcerpm: glibc-2.34-275.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/glibc-minimal-langpack-2.34-275.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 30881 + checksum: sha256:b1348c4c4fe3da979f342b0c27ff0d33a11688274a0b6708292d7750bbc8d853 + name: glibc-minimal-langpack + evr: 2.34-275.el9_8 + sourcerpm: glibc-2.34-275.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gmp-6.2.0-13.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 275679 + checksum: sha256:df01d909e4613514b1844d6ca26d0bcdff8a659762e507188d04ed046fb0cec4 + name: gmp + evr: 1:6.2.0-13.el9 + sourcerpm: gmp-6.2.0-13.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/grep-3.6-5.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 276244 + checksum: sha256:583a247a199901d44dc8a96d46010e15f6211f98f7c61ba089825155b0562520 + name: grep + evr: 3.6-5.el9 + sourcerpm: grep-3.6-5.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/g/gzip-1.12-2.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 171305 + checksum: sha256:efafc848fdaa3a8e5e77baddc07abc7d800dc973efd44ecf492bc64dca4fabe6 + name: gzip + evr: 1.12-2.el9_8 + sourcerpm: gzip-1.12-2.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/j/json-c-0.14-11.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 45052 + checksum: sha256:fff625bf4f0753eb7323b8933d264f3cc5c992bfecba8b082b204849297149cc + name: json-c + evr: 0.14-11.el9 + sourcerpm: json-c-0.14-11.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/k/keyutils-libs-1.6.3-1.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 34341 + checksum: sha256:d747ed6e1916d8ea400c89ad6078a8c298e30d652ec21985c93539e34c587a73 + name: keyutils-libs + evr: 1.6.3-1.el9 + sourcerpm: keyutils-1.6.3-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/k/krb5-libs-1.21.1-10.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 793489 + checksum: sha256:f8bbc9abe0da1ebe5bc028154bd54d465fe0682ddbb64c45882b84ff09f40e1d + name: krb5-libs + evr: 1.21.1-10.el9_8 + sourcerpm: krb5-1.21.1-10.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/less-590-6.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 165028 + checksum: sha256:fa762484ba40e0b7eb1c25531a66a0b578b6141cabb6f73d865c13ccdf75c1c9 + name: less + evr: 590-6.el9 + sourcerpm: less-590-6.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libacl-2.4.0-1.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 31468 + checksum: sha256:70ba010505e9805254f772c3dd9cd9e6176fc9e007e8c9be7204c44f85d8bbd2 + name: libacl + evr: 2.4.0-1.el9_8 + sourcerpm: acl-2.4.0-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libarchive-3.5.3-11.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 400797 + checksum: sha256:7fd7a2981416def9d96892ea11aec3b9af146a95cae11af59a2fdee22fb52516 + name: libarchive + evr: 3.5.3-11.el9_8 + sourcerpm: libarchive-3.5.3-11.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libatomic-11.5.0-14.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 26108 + checksum: sha256:bebe2e8fd98c64d1667e3de1eb3751b7b641bf5d0cd870ed6445fea5c4526326 + name: libatomic + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libattr-2.6.0-1.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 23276 + checksum: sha256:ec08036348dbe2ee41645bdb96eb485b9db5121ec0524258b0639426a22a49bc + name: libattr + evr: 2.6.0-1.el9_8 + sourcerpm: attr-2.6.0-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libblkid-2.37.4-25.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 113931 + checksum: sha256:2c38ac06d3a267b62fc5ea4e149a25c4287c19157f4f18e0f7edea4787b27e15 + name: libblkid + evr: 2.37.4-25.el9 + sourcerpm: util-linux-2.37.4-25.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libbrotli-1.0.9-9.el9_7.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 325179 + checksum: sha256:f5237abc90191238333c1214da97b5202c8a15c2be3ab401ee10d95343cfdf17 + name: libbrotli + evr: 1.0.9-9.el9_7 + sourcerpm: brotli-1.0.9-9.el9_7.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcap-2.48-10.el9_8.1.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 78021 + checksum: sha256:1ac3014c33b84d7a492b99d46d47940b096e034a3d5886e16ace7159724be012 + name: libcap + evr: 2.48-10.el9_8.1 + sourcerpm: libcap-2.48-10.el9_8.1.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcap-ng-0.8.2-7.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 36033 + checksum: sha256:dc4eae31749196c0043225c6749e7306ff71f081c09cbdb2fc98a561087c4474 + name: libcap-ng + evr: 0.8.2-7.el9 + sourcerpm: libcap-ng-0.8.2-7.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcbor-0.7.0-5.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 59368 + checksum: sha256:93a2f44044ab11225b1123bc9df4f4d09c0a5f3251818e7d144ca64fd12c0957 + name: libcbor + evr: 0.7.0-5.el9 + sourcerpm: libcbor-0.7.0-5.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcom_err-1.46.5-8.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 26607 + checksum: sha256:b7e5c8fe9d9f15864966f46c124659de6cb9137d01e213b3e8cac00d10aab55a + name: libcom_err + evr: 1.46.5-8.el9 + sourcerpm: e2fsprogs-1.46.5-8.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.7.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 292483 + checksum: sha256:408a3da221a1d3856a4b9ab7a7c70901430f71521ef9d05f9b847c0deb27b160 + name: libcurl + evr: 7.76.1-40.el9_8.7 + sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libdb-5.3.28-57.el9_6.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 727417 + checksum: sha256:3a912b2a0a6226695a5773138ce5ce090c9fb155151dffe732b8d52e6dd22d63 + name: libdb + evr: 5.3.28-57.el9_6 + sourcerpm: libdb-5.3.28-57.el9_6.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libeconf-0.4.1-7.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 32324 + checksum: sha256:8a5e117c2690c82835c6013f1fbac37b026f3e953fbffbd84c2f5ef6cf8df571 + name: libeconf + evr: 0.4.1-7.el9_8 + sourcerpm: libeconf-0.4.1-7.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libedit-3.1-39.20210216cvs.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 110092 + checksum: sha256:93964f8c06574404f4a5b44781b698f556fbc22f7ae3c82d4d540619772f6816 + name: libedit + evr: 3.1-39.20210216cvs.el9 + sourcerpm: libedit-3.1-39.20210216cvs.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libevent-2.1.13-1.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 268476 + checksum: sha256:27a85302d64c15bfd383929eedcdeabef9bfc8f0a1e085b0501d0de73cb2bf74 + name: libevent + evr: 2.1.13-1.el9_8 + sourcerpm: libevent-2.1.13-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libfdisk-2.37.4-25.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 156711 + checksum: sha256:af043918fc50ce5b3de50c48c3de0143140b692fbf639db6f259270c4211a776 + name: libfdisk + evr: 2.37.4-25.el9 + sourcerpm: util-linux-2.37.4-25.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libffi-3.4.2-8.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 38554 + checksum: sha256:d33e180b97a603542cb6f1a78b1c3b0ce4af1bc59ee0bb32620c98a629726bc4 + name: libffi + evr: 3.4.2-8.el9 + sourcerpm: libffi-3.4.2-8.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libfido2-1.13.0-2.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 100573 + checksum: sha256:e56e963635b92f407471c7c5698d602135b135bda4515ecc75ac52dd1d38c7e4 + name: libfido2 + evr: 1.13.0-2.el9 + sourcerpm: libfido2-1.13.0-2.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgcc-11.5.0-14.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 81211 + checksum: sha256:6923218fdef581189a4b51c7ba158083597f1c6df08cae021a1d90e9d61938a9 + name: libgcc + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgcrypt-1.10.0-13.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 469908 + checksum: sha256:4a270fae0cf2f5ad846ce530664bbde72e798bb4a8196afb8fd2db93086c31c9 + name: libgcrypt + evr: 1.10.0-13.el9_8 + sourcerpm: libgcrypt-1.10.0-13.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgomp-11.5.0-14.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 262138 + checksum: sha256:c8b3788fee442304e4158c802ff413df27d394b82f19c0f34ff43b5d3760470c + name: libgomp + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libgpg-error-1.42-5.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 222476 + checksum: sha256:aee968114aed0238eb26cff42ec9b0819ad32e2bac99aa8124d55b480806aca5 + name: libgpg-error + evr: 1.42-5.el9 + sourcerpm: libgpg-error-1.42-5.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libidn2-2.3.0-7.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 107549 + checksum: sha256:657925cd0fc0abc03cc83ff3688e131a452ea673a5dcb815cd0fc168bf962fc7 + name: libidn2 + evr: 2.3.0-7.el9 + sourcerpm: libidn2-2.3.0-7.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libmount-2.37.4-25.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 140081 + checksum: sha256:152aee3abc8d97a37ff4ce2f5027d7e16e93ff2c77d25e900258da54e6fcc64e + name: libmount + evr: 2.37.4-25.el9 + sourcerpm: util-linux-2.37.4-25.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libnghttp2-1.43.0-6.el9_8.2.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 79650 + checksum: sha256:eefb6d331e38314bce55c28e02d67d022c83e41b4d5de91f86d9d846b381ac48 + name: libnghttp2 + evr: 1.43.0-6.el9_8.2 + sourcerpm: nghttp2-1.43.0-6.el9_8.2.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libpkgconf-1.7.3-10.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 38310 + checksum: sha256:9bdfccf6b092e0683aa6984f7c6caa737b30c0b1495e16abb03b5d1a5f8e787a + name: libpkgconf + evr: 1.7.3-10.el9 + sourcerpm: pkgconf-1.7.3-10.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libpsl-0.21.1-5.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 67300 + checksum: sha256:08968334789ba764986d3beb4745de28eb1e2ed401a03dba9d80e75e3179aa76 + name: libpsl + evr: 0.21.1-5.el9 + sourcerpm: libpsl-0.21.1-5.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libpwquality-1.4.4-8.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 125712 + checksum: sha256:1657d94bbd79f93dc7a79d474316813bde681ce3a7f62f73314ec4d630e39349 + name: libpwquality + evr: 1.4.4-8.el9 + sourcerpm: libpwquality-1.4.4-8.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libselinux-3.6-3.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 89531 + checksum: sha256:3d7249adbf19206e319cd24acc2e01b0da39975aa3e5af73bdb6c6d438108fac + name: libselinux + evr: 3.6-3.el9 + sourcerpm: libselinux-3.6-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsemanage-3.6-5.el9_6.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 120963 + checksum: sha256:233d8270827b9166ad11827599800d2a09284d29e73af09c7a12bae251a9463c + name: libsemanage + evr: 3.6-5.el9_6 + sourcerpm: libsemanage-3.6-5.el9_6.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsepol-3.6-3.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 326966 + checksum: sha256:496ed9e2d7fac9704afe764eab4c2c43b4a47e8c229c14498dd19786f98f80c0 + name: libsepol + evr: 3.6-3.el9 + sourcerpm: libsepol-3.6-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsigsegv-2.13-4.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 30566 + checksum: sha256:0998ac158161c9d5f3b97c5dc6e35becd84da0ddc5d347a8af581ada529b3b5c + name: libsigsegv + evr: 2.13-4.el9 + sourcerpm: libsigsegv-2.13-4.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libsmartcols-2.37.4-25.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 67440 + checksum: sha256:1704e73a566c920796d877c7bc3e5a96ea0c0c4194109c7b085c1128c01856af + name: libsmartcols + evr: 2.37.4-25.el9 + sourcerpm: util-linux-2.37.4-25.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libssh-0.10.4-19.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 223114 + checksum: sha256:80f3962d3eb780ca6d6d4f8c6841b003a907d758ad8ad1c3d785e9cf327672f6 + name: libssh + evr: 0.10.4-19.el9_8 + sourcerpm: libssh-0.10.4-19.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libssh-config-0.10.4-19.el9_8.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 14764 + checksum: sha256:ce06b99793b9e5abc50a58ebc3ef11037f2efbdb5f819a25f8de4994613194d4 + name: libssh-config + evr: 0.10.4-19.el9_8 + sourcerpm: libssh-0.10.4-19.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libstdc++-11.5.0-14.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 723913 + checksum: sha256:ae00c5009a2ee4682ec732cde66d3f4fcfc1a7099ba7b3701767d1748a4f2683 + name: libstdc++ + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libtasn1-4.16.0-10.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 80446 + checksum: sha256:322524934c9b1f0714d2299705dbd41ec93451078e8144babc88c51bd19f6e07 + name: libtasn1 + evr: 4.16.0-10.el9_8 + sourcerpm: libtasn1-4.16.0-10.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libtirpc-1.3.3-9.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 98735 + checksum: sha256:591a92387f21db11cb3607f566f95e1f4afe581428eec00f99539925560e1913 + name: libtirpc + evr: 1.3.3-9.el9 + sourcerpm: libtirpc-1.3.3-9.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libunistring-0.9.10-15.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 503151 + checksum: sha256:f68934935fc209e7c595c5619df75f822cc832803e3ea6de2c92e3b91b4d5008 + name: libunistring + evr: 0.9.10-15.el9 + sourcerpm: libunistring-0.9.10-15.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libutempter-1.2.1-6.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 30505 + checksum: sha256:d352371cbb7d5bd0c53fc699df953c8c1f184b056690b3c4571e57a6634015c5 + name: libutempter + evr: 1.2.1-6.el9 + sourcerpm: libutempter-1.2.1-6.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libuuid-2.37.4-25.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 32555 + checksum: sha256:4d7bb4144053a30067a82423fb6e88fd08c7a69bd256eb21b08c0e3f4dbbaee6 + name: libuuid + evr: 2.37.4-25.el9 + sourcerpm: util-linux-2.37.4-25.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libverto-0.3.2-3.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 24651 + checksum: sha256:80d6e32c111ab9c0b2c607475b6a6691cdf6abaec19fde27043e8710a94a8f0c + name: libverto + evr: 0.3.2-3.el9 + sourcerpm: libverto-0.3.2-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libxcrypt-4.4.18-3.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 127655 + checksum: sha256:f05030123425a5033bcca3f260313cafc199bc7bca57e9fb13c335bd087c35a7 + name: libxcrypt + evr: 4.4.18-3.el9 + sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libxml2-2.9.13-14.el9_8.4.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 754646 + checksum: sha256:10417dde519f111c6248fae0eb6fb9889efd3e68c575caced652823d7ef4f169 + name: libxml2 + evr: 2.9.13-14.el9_8.4 + sourcerpm: libxml2-2.9.13-14.el9_8.4.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/libzstd-1.5.5-1.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 283159 + checksum: sha256:1229ed44dc7a68278682d7697c41d0abd7daedd242d90c6dc58a9aa6e76f9e6f + name: libzstd + evr: 1.5.5-1.el9 + sourcerpm: zstd-1.5.5-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/l/lz4-libs-1.9.3-5.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 70696 + checksum: sha256:e1dbd2c38a65b135427c7c8fe988ea70dc95f7e26c4c8177b7dcb23925020015 + name: lz4-libs + evr: 1.9.3-5.el9 + sourcerpm: lz4-1.9.3-5.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/m/make-4.3-8.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 550249 + checksum: sha256:351a22b0e6744bd329b1b0f22d9c3b69a6da970b575e6c76190cc84b0fe77450 + name: make + evr: 1:4.3-8.el9 + sourcerpm: make-4.3-8.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/m/mpfr-4.1.0-10.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 249973 + checksum: sha256:c238f7451d1fcc5431bef2904ba56a0bea51d28337ccb692f6d6a09286043a65 + name: mpfr + evr: 4.1.0-10.el9 + sourcerpm: mpfr-4.1.0-10.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/n/ncurses-base-6.2-12.20210508.el9.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 97840 + checksum: sha256:d62dfd41f9688efa2cf1ceedb96084c63e297fbdcfd1e72bc6757c730092b60c + name: ncurses-base + evr: 6.2-12.20210508.el9 + sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/n/ncurses-libs-6.2-12.20210508.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 324624 + checksum: sha256:b5dd452392d2f97bb050c9f5e5376998652c567dcbd8f035d26659b1b551b5c9 + name: ncurses-libs + evr: 6.2-12.20210508.el9 + sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openldap-2.6.8-4.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 291500 + checksum: sha256:fd684316480b2f9a9448d550c2509e37016710ca0724ff3d17d91fa0be2bdc4e + name: openldap + evr: 2.6.8-4.el9 + sourcerpm: openldap-2.6.8-4.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssh-9.9p1-11.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 432812 + checksum: sha256:d8bcf6348f5ee9d840e7f74eaaa53801cb9c48c1184c7254dd06aa73f597c690 + name: openssh + evr: 9.9p1-11.el9_8 + sourcerpm: openssh-9.9p1-11.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssh-clients-9.9p1-11.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 770368 + checksum: sha256:b5f49e9e5d66075859596aa71f62bc8cc951d50129dd43543b6aacd22386b1c1 + name: openssh-clients + evr: 9.9p1-11.el9_8 + sourcerpm: openssh-9.9p1-11.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-3.5.8-1.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 1546056 + checksum: sha256:1ef4001b9a9df4695c80e986d3c8ceb7900bb6925f86fde09d16aff9f8d733f5 + name: openssl + evr: 1:3.5.8-1.el9_8 + sourcerpm: openssl-3.5.8-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 14220 + checksum: sha256:158193d2f965db318148ec76e9347b530ac1f5379d849012c0a04d3c50cda478 + name: openssl-fips-provider + evr: 3.0.7-11.el9_8 + sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-fips-provider-so-3.0.7-11.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 529340 + checksum: sha256:22374a51f8a529dcfcf3b3ebbb2095103e0e811a28953535e5a62e26d1233301 + name: openssl-fips-provider-so + evr: 3.0.7-11.el9_8 + sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/o/openssl-libs-3.5.8-1.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 2294194 + checksum: sha256:23134af6ea097b94d8261367db01f91604ef3b508654caf4f7399e5c142abcc8 + name: openssl-libs + evr: 1:3.5.8-1.el9_8 + sourcerpm: openssl-3.5.8-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/p11-kit-0.26.4-1.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 589112 + checksum: sha256:b9391ea6618098782c9325ccda3c9ca8bc0f3b035bd4f113a793cea18026c75e + name: p11-kit + evr: 0.26.4-1.el9_8 + sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/p11-kit-trust-0.26.4-1.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 162392 + checksum: sha256:a57761123cd5836faf3d40251d16124557ffb452443bfa14cf59ac16e6c99970 + name: p11-kit-trust + evr: 0.26.4-1.el9_8 + sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pam-1.5.1-28.el9_8.1.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 642233 + checksum: sha256:12271815e3a5d35dbc4a8e1e3b52442b0d00b833816ef3a923da427a3bca561a + name: pam + evr: 1.5.1-28.el9_8.1 + sourcerpm: pam-1.5.1-28.el9_8.1.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pcre-8.44-4.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 187289 + checksum: sha256:099feef7e71b82cf0234e37d824fc81353d51dee55694e05181fa686ab50efae + name: pcre + evr: 8.44-4.el9 + sourcerpm: pcre-8.44-4.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pcre2-10.40-6.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 224938 + checksum: sha256:29285f81cef68f73b4f8ff81ee8fdf4ceaa007933302119ed1615e4aa1091613 + name: pcre2 + evr: 10.40-6.el9 + sourcerpm: pcre2-10.40-6.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pcre2-syntax-10.40-6.el9.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 147926 + checksum: sha256:d386b5e9b3a4b077b2ba143882e605750855dd3354f13c55fa12ed26908cb442 + name: pcre2-syntax + evr: 10.40-6.el9 + sourcerpm: pcre2-10.40-6.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pkgconf-1.7.3-10.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 45196 + checksum: sha256:aa38a3951a690d721a815ea8f9b01995a85f35a8540d8075205821011d0385e6 + name: pkgconf + evr: 1.7.3-10.el9 + sourcerpm: pkgconf-1.7.3-10.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pkgconf-m4-1.7.3-10.el9.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 16054 + checksum: sha256:91bafd6e06099451f60288327b275cfcc651822f6145176a157c6b0fa5131e02 + name: pkgconf-m4 + evr: 1.7.3-10.el9 + sourcerpm: pkgconf-1.7.3-10.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/pkgconf-pkg-config-1.7.3-10.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 12398 + checksum: sha256:47f1f744f96a2f3d360bc129837738dcebb1ee5032effc4472a891eea1d6a907 + name: pkgconf-pkg-config + evr: 1.7.3-10.el9 + sourcerpm: pkgconf-1.7.3-10.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/publicsuffix-list-dafsa-20210518-3.el9.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 60882 + checksum: sha256:e6ec3390a736b085f403168c512a6b2b6f8e12a8fd5a4459f1c7dbbff2b67c33 + name: publicsuffix-list-dafsa + evr: 20210518-3.el9 + sourcerpm: publicsuffix-list-20210518-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-3.9.25-7.el9_8.3.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 33143 + checksum: sha256:67e6d2eca7f6558030dd215a4d2d3ede810231faad0f317eb2bcbc7e9ac619ce + name: python3 + evr: 3.9.25-7.el9_8.3 + sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-libs-3.9.25-7.el9_8.3.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 8470487 + checksum: sha256:014bbdef3d3d00d09c34df9aaf377337e338e31c4106da6c98b844339fcd50d6 + name: python3-libs + evr: 3.9.25-7.el9_8.3 + sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-pip-wheel-21.3.1-2.el9_8.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 1198443 + checksum: sha256:918041963ad5c3b91276bf1ed3307280673ca8352e2e632bbb3847b33d2bc15a + name: python3-pip-wheel + evr: 21.3.1-2.el9_8 + sourcerpm: python-pip-21.3.1-2.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/p/python3-setuptools-wheel-53.0.0-15.el9.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 479203 + checksum: sha256:36dacb345e21bc0308ef2508f0c93995520a15ef0b56aab3593186c8dc9c0c5a + name: python3-setuptools-wheel + evr: 53.0.0-15.el9 + sourcerpm: python-setuptools-53.0.0-15.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/r/readline-8.1-4.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 219015 + checksum: sha256:2ae424b368c6747124b51b205b9e11d74aeaff56b3de90e8cbd36012e0d17707 + name: readline + evr: 8.1-4.el9 + sourcerpm: readline-8.1-4.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/r/redhat-release-9.8-1.0.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 61683 + checksum: sha256:fa7f1d93927c7f8c6f6563a8d221af659074f026e4b12cd74d456b0db1878164 + name: redhat-release + evr: 9.8-1.0.el9 + sourcerpm: redhat-release-9.8-1.0.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/sed-4.8-10.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 315893 + checksum: sha256:b73d314a8ef322a690bb69c49cb0dbd9a5ff18d2ba6b2973e18d2c076a52b62a + name: sed + evr: 4.8-10.el9 + sourcerpm: sed-4.8-10.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/setup-2.13.7-10.el9.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 153791 + checksum: sha256:0891d395ce067121c28932534237ad1ce231f2bfa987411ad62e73a12d11eb6a + name: setup + evr: 2.13.7-10.el9 + sourcerpm: setup-2.13.7-10.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/shadow-utils-4.9-16.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 1244527 + checksum: sha256:ccc46a8ea5f30d071e075ad53b5d39d191cfca4614090435528f2d2f944f88a2 + name: shadow-utils + evr: 2:4.9-16.el9 + sourcerpm: shadow-utils-4.9-16.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/sqlite-libs-3.34.1-11.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 660770 + checksum: sha256:0fbb8043f9c02870c831da433f69b856a6674d02b60306d9cc01149ec89ed239 + name: sqlite-libs + evr: 3.34.1-11.el9_8 + sourcerpm: sqlite-3.34.1-11.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/s/systemd-libs-252-67.el9_8.6.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 629233 + checksum: sha256:55e58c413ee69f19a69ba25905f96343076e21abe54cdff51f0d80e48be06769 + name: systemd-libs + evr: 252-67.el9_8.6 + sourcerpm: systemd-252-67.el9_8.6.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/t/tar-1.34-13.el9_8.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 909271 + checksum: sha256:ee4fa57c4bb87613f6fbd4b785a9e6162d43f046d1bbdd5022771652b931e9d0 + name: tar + evr: 2:1.34-13.el9_8 + sourcerpm: tar-1.34-13.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 933286 + checksum: sha256:8d6196e02853c0900c3c2e2367665beffb62cb2d0ff5f465ea2d00848a9a35dc + name: tzdata + evr: 2026c-1.el9_8 + sourcerpm: tzdata-2026c-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/u/util-linux-2.37.4-25.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 2390152 + checksum: sha256:3681bbe37d46309673f366135787f5713f0ef575e3cd413cd221af2512e3634b + name: util-linux + evr: 2.37.4-25.el9 + sourcerpm: util-linux-2.37.4-25.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/u/util-linux-core-2.37.4-25.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 472949 + checksum: sha256:de7ad826dd42b383d93f6dc6b720a26d4cd4815d00c0f093c2e28037a8881424 + name: util-linux-core + evr: 2.37.4-25.el9 + sourcerpm: util-linux-2.37.4-25.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/v/vim-filesystem-8.2.2637-26.el9_8.21.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 24156 + checksum: sha256:4362190912229b1a670e20a7e46d1f20fb2d1d30e80ec22342bba1318784f742 + name: vim-filesystem + evr: 2:8.2.2637-26.el9_8.21 + sourcerpm: vim-8.2.2637-26.el9_8.21.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/x/xz-5.2.5-8.el9_0.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 235798 + checksum: sha256:26ac21be6c1e396c7bcbaa9d4786e3275e996d9d78c01f75bbbc6962e6c9bef7 + name: xz + evr: 5.2.5-8.el9_0 + sourcerpm: xz-5.2.5-8.el9_0.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/x/xz-libs-5.2.5-8.el9_0.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 94569 + checksum: sha256:06931afb372ed4a6893e51558beaa6b0eab7adda0af93456fd99a081a8b80779 + name: xz-libs + evr: 5.2.5-8.el9_0 + sourcerpm: xz-5.2.5-8.el9_0.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/baseos/os/Packages/z/zlib-1.2.11-40.el9.aarch64.rpm + repoid: ubi-9-baseos-rpms + size: 94454 + checksum: sha256:2e7f193e67235130c10f5579c2d2ec92e22e4098b6d12fb2855d93b1540c60f7 + name: zlib + evr: 1.2.11-40.el9 + sourcerpm: zlib-1.2.11-40.el9.src.rpm + source: [] + module_metadata: [] +- arch: x86_64 + packages: + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cmake-3.31.8-3.el9.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 13989883 + checksum: sha256:e67ea7aef1edd470e4ec22982e97871655abcdc0990754d4e8f147d4e7de317a + name: cmake + evr: 3.31.8-3.el9 + sourcerpm: cmake-3.31.8-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cmake-data-3.31.8-3.el9.noarch.rpm + repoid: ubi-9-appstream-rpms + size: 2829291 + checksum: sha256:1cdc2e88a996c575b750483c8f562674e4b50a6ab414c7bbe6f6b641c1db7bd9 + name: cmake-data + evr: 3.31.8-3.el9 + sourcerpm: cmake-3.31.8-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cmake-filesystem-3.31.8-3.el9.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 19309 + checksum: sha256:b5ea81385a9e4e6a1ae2bb1175cd774af82f9c570a37008cde873ead466ba5f7 + name: cmake-filesystem + evr: 3.31.8-3.el9 + sourcerpm: cmake-3.31.8-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/c/cpp-11.5.0-14.el9.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 11226193 + checksum: sha256:3c0ee1cb8b72f3f5176f8945ab518eb733ccc9f950d317fb5d5ac327d0eb9c90 + name: cpp + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/e/emacs-filesystem-27.2-18.el9.noarch.rpm + repoid: ubi-9-appstream-rpms + size: 9495 + checksum: sha256:49d7b88a05a72c15b78191a987e6def04fda8e2e4ff75711f715d0c0ecadc60f + name: emacs-filesystem + evr: 1:27.2-18.el9 + sourcerpm: emacs-27.2-18.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-11.5.0-14.el9.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 33982584 + checksum: sha256:2082784165bbb246b6e5ef5921ed823f0a9709cacdf5823aa60695fd6d4819a2 + name: gcc + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/gcc-c++-11.5.0-14.el9.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 13474286 + checksum: sha256:b073d8965ad8eed7520a2a1c9b7c961232511ad9188dcc8c92356160a9d51e37 + name: gcc-c++ + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/git-core-2.52.0-1.el9.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 5293286 + checksum: sha256:6264aa556d583604f34def3674f5830a70cfee0aac283719e4df295db38acb53 + name: git-core + evr: 2.52.0-1.el9 + sourcerpm: git-2.52.0-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/glibc-devel-2.34-275.el9_8.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 46499 + checksum: sha256:a3b6ed698d21192fa7c421094a5d6648411fba4252db28c96d629778c86e6cd5 + name: glibc-devel + evr: 2.34-275.el9_8 + sourcerpm: glibc-2.34-275.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/glibc-headers-2.34-275.el9_8.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 567171 + checksum: sha256:2124192aba2e7931cdf00c2dcd4b70b71b313790c28dcf09b2fb09cc9831c86f + name: glibc-headers + evr: 2.34-275.el9_8 + sourcerpm: glibc-2.34-275.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.49.1.el9_8.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 2939221 + checksum: sha256:ac9fe2b15be1ea2445c28abcaa21b62a118e56ef5984aea1c9d204885786f99d + name: kernel-headers + evr: 5.14.0-687.49.1.el9_8 + sourcerpm: kernel-5.14.0-687.49.1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 66075 + checksum: sha256:b97b4e98c3c6f41dcfc2ceb4ffa1aba7a338b7cfd9e6c4f63e3160dd3cc033d3 + name: libmpc + evr: 1.2.1-4.el9 + sourcerpm: libmpc-1.2.1-4.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libnsl2-2.0.0-1.el9.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 33287 + checksum: sha256:052f7a182180528ba6e3c4378e5dcfb84640594a3e2e7bbe4f0167381e824ce0 + name: libnsl2 + evr: 2.0.0-1.el9 + sourcerpm: libnsl2-2.0.0-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libstdc++-devel-11.5.0-14.el9.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 2524816 + checksum: sha256:2d031d05fe073adc74919b8372763ae322615d9df23f4a2c642050ab4b389ce5 + name: libstdc++-devel + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libtool-ltdl-2.4.6-46.el9.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 38043 + checksum: sha256:44f7303229bdb4c2975f9829e3dd13dc7984e2cb53ef0f85baf894b39f605c38 + name: libtool-ltdl + evr: 2.4.6-46.el9 + sourcerpm: libtool-2.4.6-46.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libuv-1.42.0-2.el9_4.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 154427 + checksum: sha256:e1fab39251239ccaad2fb4dbe6c55ec1ae60f76d4ae81582b06e6a58e30879b2 + name: libuv + evr: 1:1.42.0-2.el9_4 + sourcerpm: libuv-1.42.0-2.el9_4.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libxcrypt-devel-4.4.18-3.el9.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 33101 + checksum: sha256:c1d171391a7d2e043a6953efd3df3e01edc9b4c6cdb54517e1608d204a5fce18 + name: libxcrypt-devel + evr: 4.4.18-3.el9 + sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/l/libxslt-1.1.34-14.el9_8.1.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 254411 + checksum: sha256:b702d5bdce34b424959427b728e319b17747a5a1249be5f908264c5cfcbddf38 + name: libxslt + evr: 1.1.34-14.el9_8.1 + sourcerpm: libxslt-1.1.34-14.el9_8.1.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/m/mpdecimal-2.5.1-3.el9.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 89670 + checksum: sha256:89a8c9951ac56bed2caa1adbcba349c021af1134b6e2df3fc0a8a60577a4f54d + name: mpdecimal + evr: 2.5.1-3.el9 + sourcerpm: mpdecimal-2.5.1-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/o/openssl-devel-3.5.8-1.el9_8.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 5039851 + checksum: sha256:8042a4b1134610ef06c27b7314a5fcd4ce887d1065d4b9e861bc3f647d7fb792 + name: openssl-devel + evr: 1:3.5.8-1.el9_8 + sourcerpm: openssl-3.5.8-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/p/python3.11-3.11.13-10.el9_8.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 32134 + checksum: sha256:a05e16b5733bceb90f8fc47ffabd7a888d4ba4c7f38af0438a1b871afcb4ea54 + name: python3.11 + evr: 3.11.13-10.el9_8 + sourcerpm: python3.11-3.11.13-10.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/p/python3.11-libs-3.11.13-10.el9_8.x86_64.rpm + repoid: ubi-9-appstream-rpms + size: 10713074 + checksum: sha256:81e36bc590fa38f5170e0b577df4401a48390170b0929667d0905e0cc4b47043 + name: python3.11-libs + evr: 3.11.13-10.el9_8 + sourcerpm: python3.11-3.11.13-10.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/p/python3.11-pip-wheel-22.3.1-6.el9.noarch.rpm + repoid: ubi-9-appstream-rpms + size: 1488665 + checksum: sha256:0e7e797af157c892a9fce0b6bf9f7b77db57250a16f049ae631e9acae79b5156 + name: python3.11-pip-wheel + evr: 22.3.1-6.el9 + sourcerpm: python3.11-pip-22.3.1-6.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/p/python3.11-setuptools-wheel-65.5.1-5.el9.noarch.rpm + repoid: ubi-9-appstream-rpms + size: 730338 + checksum: sha256:33ad44dff52114caa7b85f6e218ad5f9ccd88a538a3cf808377762ff01efb05f + name: python3.11-setuptools-wheel + evr: 65.5.1-5.el9 + sourcerpm: python3.11-setuptools-65.5.1-5.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/a/alternatives-1.24-2.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 42874 + checksum: sha256:1c520b9bf7b592d936bb347a5107702e51678e160b88ecfbba6a30e35e47d24e + name: alternatives + evr: 1.24-2.el9 + sourcerpm: chkconfig-1.24-2.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/a/audit-libs-3.1.5-8.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 130600 + checksum: sha256:637ac2995ce1a6c222772b60f6bc6e6f2829355d2c88dfb1262fb76146d985ae + name: audit-libs + evr: 3.1.5-8.el9 + sourcerpm: audit-3.1.5-8.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/basesystem-11-13.el9.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 8229 + checksum: sha256:f498b0813fa1a825d550e8e3a9e42255eabfa18e6fc96adfc6cc8fa7e16dd513 + name: basesystem + evr: 11-13.el9 + sourcerpm: basesystem-11-13.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/bash-5.1.8-9.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 1769540 + checksum: sha256:d3adf8b09aa0bf935c67aa12444e0ee02f70a82c2682bfb2b02bda0a989bb806 + name: bash + evr: 5.1.8-9.el9 + sourcerpm: bash-5.1.8-9.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/binutils-2.35.2-72.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 4821853 + checksum: sha256:bda706d43bf47267e31db8ac62fe3206122f97ff035daa6c93ce9cd5063a63ca + name: binutils + evr: 2.35.2-72.el9 + sourcerpm: binutils-2.35.2-72.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/binutils-gold-2.35.2-72.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 758393 + checksum: sha256:4d429d1030d8e1c610ba5aea83f8c63090033f440b2c8f788f0e0acd4a3c51b3 + name: binutils-gold + evr: 2.35.2-72.el9 + sourcerpm: binutils-2.35.2-72.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/b/bzip2-libs-1.0.8-11.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 46333 + checksum: sha256:948f763ed17672b8dd83356541e27a53ce97c6df38339c4416a188d452ca4d1e + name: bzip2-libs + evr: 1.0.8-11.el9 + sourcerpm: bzip2-1.0.8-11.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/ca-certificates-2025.2.80_v9.0.305-91.el9.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 1072208 + checksum: sha256:0554bf65d573950e7d550a07bf7d0b3def85ca3b45a7fbde4cce7cadd9a476a7 + name: ca-certificates + evr: 2025.2.80_v9.0.305-91.el9 + sourcerpm: ca-certificates-2025.2.80_v9.0.305-91.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/coreutils-8.32-41.el9_8.1.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 1222083 + checksum: sha256:374257c4cd69107333a7f524dd99579f3ea3ab842b66168eade0a3475fb6eea1 + name: coreutils + evr: 8.32-41.el9_8.1 + sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.1.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 2113503 + checksum: sha256:41f69eb8b2087feaa98d0228fb933b6fe8af20a4bf371cfef51e11cbd1f84b4e + name: coreutils-common + evr: 8.32-41.el9_8.1 + sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/cracklib-2.9.6-28.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 102444 + checksum: sha256:3b415381d4bd307686268ec42f646c7770f6a815de73a40a88aab7a7061b30a9 + name: cracklib + evr: 2.9.6-28.el9 + sourcerpm: cracklib-2.9.6-28.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/cracklib-dicts-2.9.6-28.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 3829431 + checksum: sha256:61c11d3c23b62016b9939f917bb7f7e03cba324eb4ad35b375387ce9f18e25a1 + name: cracklib-dicts + evr: 2.9.6-28.el9 + sourcerpm: cracklib-2.9.6-28.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/crypto-policies-20260224-1.gitea0f072.el9_8.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 98707 + checksum: sha256:a9ad86b8df75a7c9c29c6f6dd4b8f78cfa1f42b492eff0d11b82c457d37a7f9f + name: crypto-policies + evr: 20260224-1.gitea0f072.el9_8 + sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/crypto-policies-scripts-20260224-1.gitea0f072.el9_8.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 111065 + checksum: sha256:cec44db94e9132008a7fe4e1aa764a29e9c125989c9ac2411f3cf6f8fb669dfd + name: crypto-policies-scripts + evr: 20260224-1.gitea0f072.el9_8 + sourcerpm: crypto-policies-20260224-1.gitea0f072.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/c/cyrus-sasl-lib-2.1.27-22.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 786202 + checksum: sha256:a85ebdee7a9a49990f87e4709c368212e6a54ecf18c88a3dd54d823a82443898 + name: cyrus-sasl-lib + evr: 2.1.27-22.el9 + sourcerpm: cyrus-sasl-2.1.27-22.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-debuginfod-client-0.194-1.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 43826 + checksum: sha256:1635fd1ecaa9492fa925956dfd56d10063ca336619218f124ab45df0a38b41b0 + name: elfutils-debuginfod-client + evr: 0.194-1.el9 + sourcerpm: elfutils-0.194-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-default-yama-scope-0.194-1.el9.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 8949 + checksum: sha256:c473f42c0fec97e2830580110509e4522bc62d9b11761d062499354a0bf58959 + name: elfutils-default-yama-scope + evr: 0.194-1.el9 + sourcerpm: elfutils-0.194-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-libelf-0.194-1.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 205864 + checksum: sha256:00bbe4776149d8ccedcdf19dd6ceb08c3bb42ff41b98d744abe101af0b11a6c5 + name: elfutils-libelf + evr: 0.194-1.el9 + sourcerpm: elfutils-0.194-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/elfutils-libs-0.194-1.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 274670 + checksum: sha256:ad4f6d425cbc975cead56a2c982e38b0146d00944cbd9b3072d3d1f1271237a5 + name: elfutils-libs + evr: 0.194-1.el9 + sourcerpm: elfutils-0.194-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.3.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 128577 + checksum: sha256:3c9c96529f94f84fc19c8039d8852269e1085d16b5af2933f44ff286dec66a35 + name: expat + evr: 2.5.0-6.el9_8.3 + sourcerpm: expat-2.5.0-6.el9_8.3.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/f/filesystem-3.16-5.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 5003807 + checksum: sha256:9567592e6e32a9ebd45584cc4feb5d00812f143fcb2d8cd8b1d95108f4f66a2d + name: filesystem + evr: 3.16-5.el9 + sourcerpm: filesystem-3.16-5.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/f/findutils-4.8.0-7.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 563531 + checksum: sha256:a6328afea0a11647b7fb5c48436f0af6c795407bac0650676d3196dd47070de6 + name: findutils + evr: 1:4.8.0-7.el9 + sourcerpm: findutils-4.8.0-7.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gawk-5.1.0-6.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 1045534 + checksum: sha256:99fda6725a2c668bae29fbab74d1b347e074f4e8c8ed18d656cb928fb6fc92b7 + name: gawk + evr: 5.1.0-6.el9 + sourcerpm: gawk-5.1.0-6.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 60152 + checksum: sha256:c8b8346a98d921206666ce740a3647a52ad7a87c2d01d73166165b3e9a789a6c + name: gdbm-libs + evr: 1:1.23-1.el9 + sourcerpm: gdbm-1.23-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-2.34-275.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 2083064 + checksum: sha256:7d2d420b97c05c09ee1e9bd881cb0725fe8d89688b933f411f278cb23210c65d + name: glibc + evr: 2.34-275.el9_8 + sourcerpm: glibc-2.34-275.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-common-2.34-275.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 321585 + checksum: sha256:f23581b888783f576bd3a55503618a48f74f468fcfd4837bbd7a2d00fe7f3530 + name: glibc-common + evr: 2.34-275.el9_8 + sourcerpm: glibc-2.34-275.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/glibc-minimal-langpack-2.34-275.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 30913 + checksum: sha256:6cc48d78bf2ceacfa5b58633b648c564b66505f4677adea8eb663fe90acd76f2 + name: glibc-minimal-langpack + evr: 2.34-275.el9_8 + sourcerpm: glibc-2.34-275.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gmp-6.2.0-13.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 326840 + checksum: sha256:d4529445e30b7eb9a8225b0539f70d26d585d7fe306296f948ea73114d1c171f + name: gmp + evr: 1:6.2.0-13.el9 + sourcerpm: gmp-6.2.0-13.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/grep-3.6-5.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 279174 + checksum: sha256:5556895ff1817066ca71b50785615e944b0fcc7e1c94c983087c7c691819623d + name: grep + evr: 3.6-5.el9 + sourcerpm: grep-3.6-5.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/g/gzip-1.12-2.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 172571 + checksum: sha256:a87bdcce45011f232758c01bd99c564b5f6b549d391646cc573a132d22604b85 + name: gzip + evr: 1.12-2.el9_8 + sourcerpm: gzip-1.12-2.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/j/json-c-0.14-11.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 46136 + checksum: sha256:b9bde4162250023103d95908fbca44fff6636a46176f92cf1761c1c3a4580a2f + name: json-c + evr: 0.14-11.el9 + sourcerpm: json-c-0.14-11.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/k/keyutils-libs-1.6.3-1.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 34363 + checksum: sha256:96d75824948387a884d206865db534cd3d46f32422efcb020c20060b59edb27c + name: keyutils-libs + evr: 1.6.3-1.el9 + sourcerpm: keyutils-1.6.3-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/k/krb5-libs-1.21.1-10.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 790743 + checksum: sha256:8906be9f2d414c5f4e1218db0c94955c2b3394b43a04b7dbcc2ef66c4340ebc6 + name: krb5-libs + evr: 1.21.1-10.el9_8 + sourcerpm: krb5-1.21.1-10.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/less-590-6.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 166025 + checksum: sha256:5bd040f9dd813167935fc390d546c119d90e0a9c77447a3d9ed1ef69c6f5a32a + name: less + evr: 590-6.el9 + sourcerpm: less-590-6.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libacl-2.4.0-1.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 31657 + checksum: sha256:a81fb7a4d7c946e9bd886ee3c471a4b5040dedbb8ffb2a388120b2094be93cc8 + name: libacl + evr: 2.4.0-1.el9_8 + sourcerpm: acl-2.4.0-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libarchive-3.5.3-11.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 402853 + checksum: sha256:3b8ed4523d8721a1fabc2c92e6871c353b8ff5fb555663ab006fe90a7be35a86 + name: libarchive + evr: 3.5.3-11.el9_8 + sourcerpm: libarchive-3.5.3-11.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libattr-2.6.0-1.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 23752 + checksum: sha256:9e37537f690c748f7f05faa80966072f118ec722e38ef332fe19cf22b087349f + name: libattr + evr: 2.6.0-1.el9_8 + sourcerpm: attr-2.6.0-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libblkid-2.37.4-25.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 114192 + checksum: sha256:a858400abe83a7955ae509c920f835a950ea2cf1156916823c595a23ba46d536 + name: libblkid + evr: 2.37.4-25.el9 + sourcerpm: util-linux-2.37.4-25.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libbrotli-1.0.9-9.el9_7.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 326278 + checksum: sha256:81096e6aed022489306e2fe1d1496b2b689d8f0bf6c70a94b5bddb82356eeda1 + name: libbrotli + evr: 1.0.9-9.el9_7 + sourcerpm: brotli-1.0.9-9.el9_7.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcap-2.48-10.el9_8.1.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 78928 + checksum: sha256:d4805439b10fa551b7535cf30ca28d4d5862132c9c429b2b31221bea7f43263a + name: libcap + evr: 2.48-10.el9_8.1 + sourcerpm: libcap-2.48-10.el9_8.1.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcap-ng-0.8.2-7.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 36752 + checksum: sha256:ebddfc188d1ddbb0d6a238583cbc02dcb9fc0bd063a850b22d48980899976628 + name: libcap-ng + evr: 0.8.2-7.el9 + sourcerpm: libcap-ng-0.8.2-7.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcbor-0.7.0-5.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 60575 + checksum: sha256:588e8736af3376abfb3cdf372c10baef02c40d916a55958f3bee9767f9ad8526 + name: libcbor + evr: 0.7.0-5.el9 + sourcerpm: libcbor-0.7.0-5.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcom_err-1.46.5-8.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 26980 + checksum: sha256:b7593ee2d841c69573d8ed553b7416ef727b2c77c0473416a5dadf4b567bf547 + name: libcom_err + evr: 1.46.5-8.el9 + sourcerpm: e2fsprogs-1.46.5-8.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.7.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 296930 + checksum: sha256:044d7a379f0f0f6ed25b9d41439dedfb55b390108ae5a09d4acc6b3565c39ae1 + name: libcurl + evr: 7.76.1-40.el9_8.7 + sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libdb-5.3.28-57.el9_6.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 755192 + checksum: sha256:3246e76f197e2b60eb470b9b55d3e0dda2301b029f295fed9c38ff70b87c5b6b + name: libdb + evr: 5.3.28-57.el9_6 + sourcerpm: libdb-5.3.28-57.el9_6.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libeconf-0.4.1-7.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 33179 + checksum: sha256:a570c5baaedeb1445bc2e4f3930b0a709411bbefbdbf463c3e006cbfc7018894 + name: libeconf + evr: 0.4.1-7.el9_8 + sourcerpm: libeconf-0.4.1-7.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libedit-3.1-39.20210216cvs.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 112056 + checksum: sha256:65a730688dfea27934b75af3acf30150c6d254c89d8b68b63233ae7f8b6c9b94 + name: libedit + evr: 3.1-39.20210216cvs.el9 + sourcerpm: libedit-3.1-39.20210216cvs.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libevent-2.1.13-1.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 276162 + checksum: sha256:c28b28573118d5cf0f1f68d96bc7c46d56671e45360698c894ca06ddf40163ae + name: libevent + evr: 2.1.13-1.el9_8 + sourcerpm: libevent-2.1.13-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libfdisk-2.37.4-25.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 161769 + checksum: sha256:1b861f267752e718ce696a80dcc06ef1dde8e9aa73fa2abed3775626d719c124 + name: libfdisk + evr: 2.37.4-25.el9 + sourcerpm: util-linux-2.37.4-25.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libffi-3.4.2-8.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 40619 + checksum: sha256:dde0012a94c6f3825e605b095b15767d89c2b87a5da097348310d7e87721c645 + name: libffi + evr: 3.4.2-8.el9 + sourcerpm: libffi-3.4.2-8.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libfido2-1.13.0-2.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 102746 + checksum: sha256:6da940c0528f3e4453db84cb85b402c8f4293a197b1921158df9651edb4845e0 + name: libfido2 + evr: 1.13.0-2.el9 + sourcerpm: libfido2-1.13.0-2.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgcc-11.5.0-14.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 87280 + checksum: sha256:77c66827ffc14df2f43612b26128b1fd58d5c9597d4d4e564aa239b161272872 + name: libgcc + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgcrypt-1.10.0-13.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 523829 + checksum: sha256:c07cd9f613809195b8691d28fd2f3bff55b2a83b9c1cdf4a32c681e0e32dfafb + name: libgcrypt + evr: 1.10.0-13.el9_8 + sourcerpm: libgcrypt-1.10.0-13.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgomp-11.5.0-14.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 263723 + checksum: sha256:87b9a7316760374111290e6e4c76ee4d093566f08a7c7c91ad7827c5948afb69 + name: libgomp + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libgpg-error-1.42-5.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 225603 + checksum: sha256:8248e20d7a253aa9c0dc7dc3d56b42e1def4fd5753ce8e8b9e980aa664fc9068 + name: libgpg-error + evr: 1.42-5.el9 + sourcerpm: libgpg-error-1.42-5.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libidn2-2.3.0-7.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 107099 + checksum: sha256:055f4ce6b721be7138dc2e45a6586412c65508acea3fe385a2655c129fe264f9 + name: libidn2 + evr: 2.3.0-7.el9 + sourcerpm: libidn2-2.3.0-7.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libmount-2.37.4-25.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 142467 + checksum: sha256:a9a2022eb9e39301bfcd3273573a108486b2b315c89247f147870016b2e9222c + name: libmount + evr: 2.37.4-25.el9 + sourcerpm: util-linux-2.37.4-25.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libnghttp2-1.43.0-6.el9_8.2.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 80410 + checksum: sha256:adb3260b6610917c07bda0a6bc521d5628bb1892a66d008f0fea81dc022ac699 + name: libnghttp2 + evr: 1.43.0-6.el9_8.2 + sourcerpm: nghttp2-1.43.0-6.el9_8.2.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libpkgconf-1.7.3-10.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 38387 + checksum: sha256:4feae5941b73640bd86b8d506a657cac5b770043db1464fbcd207721b2159dda + name: libpkgconf + evr: 1.7.3-10.el9 + sourcerpm: pkgconf-1.7.3-10.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libpsl-0.21.1-5.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 67454 + checksum: sha256:ad1a62ef07682bb64a476c1a49f5cfc7abc9beb44775e7e511bf737e9a6bf99d + name: libpsl + evr: 0.21.1-5.el9 + sourcerpm: libpsl-0.21.1-5.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libpwquality-1.4.4-8.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 126104 + checksum: sha256:14b7ff2f7fdaf8ebec90261f4619ea7f7c3564c4de8483666de7ed4b1f49b66f + name: libpwquality + evr: 1.4.4-8.el9 + sourcerpm: libpwquality-1.4.4-8.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libselinux-3.6-3.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 89722 + checksum: sha256:ce1cc63a7212c39f5f2a35f719ee38d6418cf081ea78c9317f388d9f41e4a627 + name: libselinux + evr: 3.6-3.el9 + sourcerpm: libselinux-3.6-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsemanage-3.6-5.el9_6.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 123449 + checksum: sha256:7ac29f46714cd762f18a52e9807fd1766b0cf9e0388aa3d9befaabf8785a01e3 + name: libsemanage + evr: 3.6-5.el9_6 + sourcerpm: libsemanage-3.6-5.el9_6.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsepol-3.6-3.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 338766 + checksum: sha256:b98984b2bf42203964cc979ac157df090c63b89a0f5c6560ede01965531c8ffd + name: libsepol + evr: 3.6-3.el9 + sourcerpm: libsepol-3.6-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsigsegv-2.13-4.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 30681 + checksum: sha256:24005c62017797b612d047a2af83a218633b32302a787fabd22e52230db6adc1 + name: libsigsegv + evr: 2.13-4.el9 + sourcerpm: libsigsegv-2.13-4.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libsmartcols-2.37.4-25.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 68692 + checksum: sha256:c1da912799780b89cd44db4acc318ea4a83adba0d8d99aad1b877879f40dbd48 + name: libsmartcols + evr: 2.37.4-25.el9 + sourcerpm: util-linux-2.37.4-25.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libssh-0.10.4-19.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 225821 + checksum: sha256:10d0ecb7cef182f8d11eb4bc772943a60c48b8171f602ffd83bb79b9edf5eb44 + name: libssh + evr: 0.10.4-19.el9_8 + sourcerpm: libssh-0.10.4-19.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libssh-config-0.10.4-19.el9_8.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 14764 + checksum: sha256:ce06b99793b9e5abc50a58ebc3ef11037f2efbdb5f819a25f8de4994613194d4 + name: libssh-config + evr: 0.10.4-19.el9_8 + sourcerpm: libssh-0.10.4-19.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libstdc++-11.5.0-14.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 763021 + checksum: sha256:513df35338962e053052b9d52429e8a5f3d60dfe6b1757cd9faaf61d6abda955 + name: libstdc++ + evr: 11.5.0-14.el9 + sourcerpm: gcc-11.5.0-14.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libtasn1-4.16.0-10.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 81418 + checksum: sha256:f9473f322407f10205b0db98b89cf8f603e9c769e9977250734136df56cbb981 + name: libtasn1 + evr: 4.16.0-10.el9_8 + sourcerpm: libtasn1-4.16.0-10.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libtirpc-1.3.3-9.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 98934 + checksum: sha256:f82cd69dc3aac881d5b574930c7d274687054cb5b03d3a8e3affa7bbcd5950b1 + name: libtirpc + evr: 1.3.3-9.el9 + sourcerpm: libtirpc-1.3.3-9.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libunistring-0.9.10-15.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 510558 + checksum: sha256:6477fb3c3285158f676360e228057e13dc6e983f453c7c74ed4ab140357f9a0d + name: libunistring + evr: 0.9.10-15.el9 + sourcerpm: libunistring-0.9.10-15.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libutempter-1.2.1-6.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 30354 + checksum: sha256:0f1df5e0d48c2ac9914bfffa7ed569cd58e42b17ba96bb3f7cf74d1e80de2597 + name: libutempter + evr: 1.2.1-6.el9 + sourcerpm: libutempter-1.2.1-6.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libuuid-2.37.4-25.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 32757 + checksum: sha256:5694aafca42c707f85af66bba11d102f7636ee17f586466b3ff80254e995ed7b + name: libuuid + evr: 2.37.4-25.el9 + sourcerpm: util-linux-2.37.4-25.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libverto-0.3.2-3.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 25042 + checksum: sha256:7008029afd91af33ca17a22e6eb4ba792fd9b32bee8fb613c79c1527fa6f589a + name: libverto + evr: 0.3.2-3.el9 + sourcerpm: libverto-0.3.2-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libxcrypt-4.4.18-3.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 122599 + checksum: sha256:a50bb26a28ee7e6379c86b5b91285299b71569fa87ea968d800a56090b7a179d + name: libxcrypt + evr: 4.4.18-3.el9 + sourcerpm: libxcrypt-4.4.18-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libxml2-2.9.13-14.el9_8.4.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 772646 + checksum: sha256:3b2b7f705584d2aa9dc1a110ef1b00dbefb3305285877a24a24605fcb9567eb0 + name: libxml2 + evr: 2.9.13-14.el9_8.4 + sourcerpm: libxml2-2.9.13-14.el9_8.4.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/libzstd-1.5.5-1.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 304135 + checksum: sha256:d8a149f0d8f217126642cc4b40199d631b940f7d227191cc2179f3158fd47f9e + name: libzstd + evr: 1.5.5-1.el9 + sourcerpm: zstd-1.5.5-1.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/l/lz4-libs-1.9.3-5.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 70922 + checksum: sha256:9658da838021711f687cf283368664984bfb1c8b9176897d7d477a724a11a731 + name: lz4-libs + evr: 1.9.3-5.el9 + sourcerpm: lz4-1.9.3-5.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/m/make-4.3-8.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 553896 + checksum: sha256:561f0c2251e9217c81a6c88de4d2d9231a039aaab37e8a0d2559d36ce9fa85fd + name: make + evr: 1:4.3-8.el9 + sourcerpm: make-4.3-8.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/m/mpfr-4.1.0-10.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 338130 + checksum: sha256:4adb12cda3b0e537ba5b22e7615288df751720d2e738bd182675d529cf1ead0c + name: mpfr + evr: 4.1.0-10.el9 + sourcerpm: mpfr-4.1.0-10.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/n/ncurses-base-6.2-12.20210508.el9.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 97840 + checksum: sha256:d62dfd41f9688efa2cf1ceedb96084c63e297fbdcfd1e72bc6757c730092b60c + name: ncurses-base + evr: 6.2-12.20210508.el9 + sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/n/ncurses-libs-6.2-12.20210508.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 336270 + checksum: sha256:f3e1f8e59c7116278aa19b6705a1443f6307d4d6fbdde75a23d2f5d60636cb16 + name: ncurses-libs + evr: 6.2-12.20210508.el9 + sourcerpm: ncurses-6.2-12.20210508.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openldap-2.6.8-4.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 296805 + checksum: sha256:68df8cf8fb4d54c2f1681fa9a030f7af3b179e6dd4fd10ffd7532824121ea74c + name: openldap + evr: 2.6.8-4.el9 + sourcerpm: openldap-2.6.8-4.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssh-9.9p1-11.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 443050 + checksum: sha256:9fad2dc75044e577f03442e524b58223239eba14b12adbf8d14eeb82d22b596e + name: openssh + evr: 9.9p1-11.el9_8 + sourcerpm: openssh-9.9p1-11.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssh-clients-9.9p1-11.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 799504 + checksum: sha256:445f8ef1a60229d575547695da0bf2a05fb51408bce01d06548a5273123c8877 + name: openssh-clients + evr: 9.9p1-11.el9_8 + sourcerpm: openssh-9.9p1-11.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-3.5.8-1.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 1569041 + checksum: sha256:2e0d5017032a48c23d4efa43afbe98d45179365eaec16f38e93c271b7728a67a + name: openssl + evr: 1:3.5.8-1.el9_8 + sourcerpm: openssl-3.5.8-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 14256 + checksum: sha256:c00860e9c5a1d90488aa2eb65fe41f62926b38c6b3669d331a8b97e4a60223ac + name: openssl-fips-provider + evr: 3.0.7-11.el9_8 + sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-fips-provider-so-3.0.7-11.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 595008 + checksum: sha256:60d36ad3a67d6b00e67bb0a19c0902fbb2ecdd873cf7f280a3039d04a092790c + name: openssl-fips-provider-so + evr: 3.0.7-11.el9_8 + sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/o/openssl-libs-3.5.8-1.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 2430624 + checksum: sha256:3abe1f190415d91d4dc51db74cf2ad7e06b1e5ca5f63dac978fe58d8fd14e493 + name: openssl-libs + evr: 1:3.5.8-1.el9_8 + sourcerpm: openssl-3.5.8-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/p11-kit-0.26.4-1.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 625862 + checksum: sha256:a00ba14bfd0fc5dd2818f605f2e0520b52ea4b36167504c2523f92a065c2bdaf + name: p11-kit + evr: 0.26.4-1.el9_8 + sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/p11-kit-trust-0.26.4-1.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 165521 + checksum: sha256:41b84ab0ee4cf914d570a3d648ed98b7de44cef73ea3dfa58ff5eebdec85f42a + name: p11-kit-trust + evr: 0.26.4-1.el9_8 + sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pam-1.5.1-28.el9_8.1.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 645147 + checksum: sha256:9285aea93392dada0c8399b7d8a1c85ce7a6fdafd2fb5264727448549c15637c + name: pam + evr: 1.5.1-28.el9_8.1 + sourcerpm: pam-1.5.1-28.el9_8.1.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pcre-8.44-4.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 205261 + checksum: sha256:e9ddc7d57d4f6e7400b66bcc78b9bafc1f05630e3e0d2a14000bc907f429ddc4 + name: pcre + evr: 8.44-4.el9 + sourcerpm: pcre-8.44-4.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pcre2-10.40-6.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 241900 + checksum: sha256:75db1e5a50e7b1794d7ba18212d95cd2684559da9e7c52eee46490302c7f24dd + name: pcre2 + evr: 10.40-6.el9 + sourcerpm: pcre2-10.40-6.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pcre2-syntax-10.40-6.el9.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 147926 + checksum: sha256:d386b5e9b3a4b077b2ba143882e605750855dd3354f13c55fa12ed26908cb442 + name: pcre2-syntax + evr: 10.40-6.el9 + sourcerpm: pcre2-10.40-6.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pkgconf-1.7.3-10.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 45675 + checksum: sha256:bb47b4ecc499c308f41031a99e723827d152d5d750f59849d0c265d820944a26 + name: pkgconf + evr: 1.7.3-10.el9 + sourcerpm: pkgconf-1.7.3-10.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pkgconf-m4-1.7.3-10.el9.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 16054 + checksum: sha256:91bafd6e06099451f60288327b275cfcc651822f6145176a157c6b0fa5131e02 + name: pkgconf-m4 + evr: 1.7.3-10.el9 + sourcerpm: pkgconf-1.7.3-10.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/pkgconf-pkg-config-1.7.3-10.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 12438 + checksum: sha256:9a502d81d73d3303ceb53a06ad7ce525c97117ea64352174a33708bf3429283d + name: pkgconf-pkg-config + evr: 1.7.3-10.el9 + sourcerpm: pkgconf-1.7.3-10.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/publicsuffix-list-dafsa-20210518-3.el9.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 60882 + checksum: sha256:e6ec3390a736b085f403168c512a6b2b6f8e12a8fd5a4459f1c7dbbff2b67c33 + name: publicsuffix-list-dafsa + evr: 20210518-3.el9 + sourcerpm: publicsuffix-list-20210518-3.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-3.9.25-7.el9_8.3.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 33200 + checksum: sha256:f0d622eb17a038e98c23ef9bd6961c104c55b3534f69c301b2067cd9161f40f7 + name: python3 + evr: 3.9.25-7.el9_8.3 + sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-libs-3.9.25-7.el9_8.3.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 8483866 + checksum: sha256:6227afce7123d2e6c46a4a6d318087c512caebf6e09f7bfc0ba73f7e5853fba4 + name: python3-libs + evr: 3.9.25-7.el9_8.3 + sourcerpm: python3.9-3.9.25-7.el9_8.3.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-pip-wheel-21.3.1-2.el9_8.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 1198443 + checksum: sha256:918041963ad5c3b91276bf1ed3307280673ca8352e2e632bbb3847b33d2bc15a + name: python3-pip-wheel + evr: 21.3.1-2.el9_8 + sourcerpm: python-pip-21.3.1-2.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/p/python3-setuptools-wheel-53.0.0-15.el9.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 479203 + checksum: sha256:36dacb345e21bc0308ef2508f0c93995520a15ef0b56aab3593186c8dc9c0c5a + name: python3-setuptools-wheel + evr: 53.0.0-15.el9 + sourcerpm: python-setuptools-53.0.0-15.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/r/readline-8.1-4.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 220174 + checksum: sha256:01bf315b3bc44c28515c4d33d49173b23d7979d2a09b7b15f749d434b60851e6 + name: readline + evr: 8.1-4.el9 + sourcerpm: readline-8.1-4.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/r/redhat-release-9.8-1.0.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 61742 + checksum: sha256:8157ed988fc34dcfeb6429272959471edd5bde4ac212f26611fd54c180391758 + name: redhat-release + evr: 9.8-1.0.el9 + sourcerpm: redhat-release-9.8-1.0.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/sed-4.8-10.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 317456 + checksum: sha256:45e246453dc9eb1bad6a71c6f349aad1b1b2e1bf3ec645b80f0ed04fe69c960e + name: sed + evr: 4.8-10.el9 + sourcerpm: sed-4.8-10.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/setup-2.13.7-10.el9.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 153791 + checksum: sha256:0891d395ce067121c28932534237ad1ce231f2bfa987411ad62e73a12d11eb6a + name: setup + evr: 2.13.7-10.el9 + sourcerpm: setup-2.13.7-10.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/shadow-utils-4.9-16.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 1250179 + checksum: sha256:17294ee3fbc09c1b5cfc4114d3815cbd92584d6d70a6288e1dce2fda0a62dc59 + name: shadow-utils + evr: 2:4.9-16.el9 + sourcerpm: shadow-utils-4.9-16.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/sqlite-libs-3.34.1-11.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 665095 + checksum: sha256:e5c20e933ec01f746a6c59a94cb99f46c6138dab3f387f0d02dab47f356e2e98 + name: sqlite-libs + evr: 3.34.1-11.el9_8 + sourcerpm: sqlite-3.34.1-11.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/s/systemd-libs-252-67.el9_8.6.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 662007 + checksum: sha256:d491d7375ed820bf4fa0d5f6d80a6f1a72cfa6ff31e79dabf9288246061cf88e + name: systemd-libs + evr: 252-67.el9_8.6 + sourcerpm: systemd-252-67.el9_8.6.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/t/tar-1.34-13.el9_8.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 914200 + checksum: sha256:913d84cd94463e3f0400d80c6742a2974eeab6445ac71ff9eb802a70779c146f + name: tar + evr: 2:1.34-13.el9_8 + sourcerpm: tar-1.34-13.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 933286 + checksum: sha256:8d6196e02853c0900c3c2e2367665beffb62cb2d0ff5f465ea2d00848a9a35dc + name: tzdata + evr: 2026c-1.el9_8 + sourcerpm: tzdata-2026c-1.el9_8.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/u/util-linux-2.37.4-25.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 2382511 + checksum: sha256:35e0b73e574a7d8e93af0adf54adb2303f03423fd5761e357df86288f59d7933 + name: util-linux + evr: 2.37.4-25.el9 + sourcerpm: util-linux-2.37.4-25.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/u/util-linux-core-2.37.4-25.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 476811 + checksum: sha256:5575c8fc753d5a81022786dac33e172a6d1602ef41d247a58465754d3f952726 + name: util-linux-core + evr: 2.37.4-25.el9 + sourcerpm: util-linux-2.37.4-25.el9.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/v/vim-filesystem-8.2.2637-26.el9_8.21.noarch.rpm + repoid: ubi-9-baseos-rpms + size: 24156 + checksum: sha256:4362190912229b1a670e20a7e46d1f20fb2d1d30e80ec22342bba1318784f742 + name: vim-filesystem + evr: 2:8.2.2637-26.el9_8.21 + sourcerpm: vim-8.2.2637-26.el9_8.21.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/x/xz-5.2.5-8.el9_0.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 235693 + checksum: sha256:f16d17c26a241400586ddc3d734ce863e3f19d433881ec640a47bedf0dafd07b + name: xz + evr: 5.2.5-8.el9_0 + sourcerpm: xz-5.2.5-8.el9_0.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/x/xz-libs-5.2.5-8.el9_0.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 96649 + checksum: sha256:de263f880a4394f04b5e84254ba0a88d781b5bd63665c9e028bc10351490c982 + name: xz-libs + evr: 5.2.5-8.el9_0 + sourcerpm: xz-5.2.5-8.el9_0.src.rpm + - url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/baseos/os/Packages/z/zlib-1.2.11-40.el9.x86_64.rpm + repoid: ubi-9-baseos-rpms + size: 95708 + checksum: sha256:baf95ffbf40ee014135f16fe33e343faf7ff1ca06509fd97cd988e6afeabf670 + name: zlib + evr: 1.2.11-40.el9 + sourcerpm: zlib-1.2.11-40.el9.src.rpm + source: [] + module_metadata: [] diff --git a/e2e/rust/tests/odh/AGENTS.md b/e2e/rust/tests/odh/AGENTS.md index f2fd945460..dcd376d1d2 100644 --- a/e2e/rust/tests/odh/AGENTS.md +++ b/e2e/rust/tests/odh/AGENTS.md @@ -15,6 +15,7 @@ is the source of truth for: - the image-provenance check and its required env vars (`ALLOWED_IMAGE_REGISTRY_PREFIXES`, `NAMESPACE`, `RELEASE`, `SKIP_IMAGE_PROVENANCE`); - the `KUBECONFIG` isolation gotcha for `mise` tasks; +- the Konflux e2e image, its gateway lifecycle, and report artifacts; - rebase guidance for keeping this fork-only directory additive against `NVIDIA/OpenShell`. @@ -24,16 +25,20 @@ a rule and the README disagree, fix the drift rather than guessing. ## Fork-only, rebase-safe - Everything under `e2e/rust/tests/odh/` and `tasks/test-odh.toml` is fork-only - and must stay that way. Do not add fork-specific content to any upstream file. -- The only upstream file this work may touch is `e2e/rust/Cargo.toml`, and only - via the two appended additive blocks (the `e2e-odh` feature and the `odh` - `[[test]]` entry). Never edit the root `AGENTS.md` for ODH-specific rules — - put them here instead. + and must stay that way. Keep ODH test logic out of upstream source files. +- Keep the upstream `e2e/rust/Cargo.toml` change limited to the appended + `e2e-odh` feature and `odh` `[[test]]` entry. The e2e image may also add + its nextest profile to `.config/nextest.toml` and additive integration in + `deploy/konflux/build-local.sh` and `.dockerignore`. Review these shared + files when syncing upstream. Keep the ODH build overview in + `deploy/konflux/e2e-odh/README.md`. Never edit the root `AGENTS.md` for + ODH-specific rules — put them here instead. ## Commit messages - Prefix the commit title of any downstream-only (fork carry) commit with - `CARRY:` so it is identifiable when rebasing against `NVIDIA/OpenShell`. + `CARRY:` followed by a space so it is identifiable when rebasing against + `NVIDIA/OpenShell`. - Keep the repository-root Conventional Commits format after the prefix, e.g. `CARRY: test(odh): add SELinux enforcing coverage`. - Sign off every commit for DCO (`git commit --signoff`) and never reference AI @@ -49,8 +54,9 @@ a rule and the README disagree, fix the drift rather than guessing. `oc debug node ... chroot /host ausearch` work the same from Rust — put them in a shared helper (see below), not a shell script. - Cluster/deployment setup (Helm values, in-cluster fixtures, proxies) is - environment setup, not a test. Keep it out of test bodies and out of new - runner scripts. Tests assume an already-deployed, working gateway. + environment setup, not a test. Keep it out of test bodies and the tier + runner. The e2e image entrypoint may deploy and tear down the gateway; + tests still assume an already-deployed, working gateway. ## Shared helpers @@ -73,11 +79,10 @@ a rule and the README disagree, fix the drift rather than guessing. - Add tests by creating a `.rs` file under the right tier and a `mod` line in that tier's `mod.rs`, and map upstream binaries/filters in `tiers.toml`. Do not add new one-off `mise` tasks for individual test lanes. -- A small, fixed set of entry points (`e2e:odh:smoke|tier1|tier2|tier3`) is a - hard requirement: the OpenShift AI Shift-Left testing pipeline consumes these - standard tier tasks as quality gates. Bespoke deploy-and-run tasks with their - own deploy semantics and host-access assumptions are hard to slot into those - gates — keep the surface stable. +- Keep the standard tier entry points + (`e2e:odh:smoke|tier1|tier2|tier3`, `e2e:odh`, and `e2e:odh:full`) + stable. The OpenShift AI Shift-Left pipeline consumes those tiers as + quality gates; the image entrypoint handles deployment around them. ## Gate environment-specific tests, don't fork the task diff --git a/e2e/rust/tests/odh/README.md b/e2e/rust/tests/odh/README.md index 290cca7c88..0382901ebd 100644 --- a/e2e/rust/tests/odh/README.md +++ b/e2e/rust/tests/odh/README.md @@ -5,10 +5,10 @@ Open Data Hub (ODH) / OpenShift AI (RHOAI) workloads. These are complementary to the upstream `e2e:kubernetes` suite: they cover ODH/RHOAI-specific behavior that upstream does not, and do not duplicate upstream coverage. -This directory is fork-only — none of it exists upstream, and none of it will -conflict on a rebase against `NVIDIA/OpenShell`. The only upstream file -touched by this work is `e2e/rust/Cargo.toml`, and that change is purely -additive (a new feature flag and a new `[[test]]` entry, both appended). +This directory is fork-only and does not currently overlap upstream files. +The ODH feature and test binary are appended to `e2e/rust/Cargo.toml` in the +fork. The e2e image also changes shared build and documentation files; review +those changes after each upstream sync. ## Directory layout @@ -32,13 +32,13 @@ e2e/rust/tests/odh/ ├── tier3/ # Tier 3: negative and destructive tests │ └── mod.rs # empty — no scenarios yet ├── tiers.toml # tier → upstream test binaries + ODH module filter -└── run-odh-test-tier.sh # runner: entrypoint for tiered execution +└── run-odh-test-tier.sh # runs one tier against a deployed gateway ``` All ODH test functions compile into a single `odh` test binary (`[[test]] name = "odh"` in `Cargo.toml`). Cargo generates test names that include the full module path, e.g. `smoke::gateway::test_reachable`, which is -what enables tier-based filtering (`-- smoke::`, `-- tier1::`, ...). +what enables tier-based nextest filters (`test(~smoke::)`, `test(~tier1::)`, ...). Adding a new test area within a tier is just adding a `.rs` file and a `mod` line in that tier's `mod.rs` — no file grows unbounded, and no other tier is @@ -80,9 +80,11 @@ duplicating logic across tier files. Two rules keep this rebase-safe: | Tier 1 | High-priority tests, excluding Smoke | 15 min or less | | Tier 2 | Medium/low priority positive tests | No limit | | Tier 3 | Negative and destructive tests | No limit | +| ODH | All ODH tests | No limit | +| Full | Every feature-enabled upstream and ODH test, except listed exclusions | No limit | -`tiers.toml` maps each tier to the upstream `[[test]]` binaries (from -`e2e/rust/Cargo.toml`) and the ODH module filter that belong to it: +`tiers.toml` maps each tier to upstream Cargo test binaries (explicit or +auto-discovered) and the ODH module filter that belong to it: ```toml [smoke] @@ -95,10 +97,19 @@ they should be revisited based on measured execution time and actual test criticality, not just copied as-is. Smoke covers gateway reachability, sandbox lifecycle, and image provenance. -Tier 1 checks the process-supervisor SELinux label. Tier 2 and Tier 3 have no -ODH scenarios yet; they run their mapped upstream tests and the image -provenance check. Add scenarios by creating a `.rs` file under the tier's -directory and declaring it with a `mod` line in that tier's `mod.rs`. +Tier 1 checks the process-supervisor SELinux label and its mapped upstream +tests. Tier 2 and Tier 3 have no ODH scenarios yet; they run their mapped +upstream tests and the image provenance check. Add scenarios by creating a +`.rs` file under the tier's directory and declaring it with a `mod` line in +that tier's `mod.rs`. + +The nextest `e2e-odh` profile runs the serial sandbox lifecycle tests and +`tier1::selinux` tests in one group with `max-threads = 1`, preserving +serialization across nextest's separate test processes. Tier 1 and Full +include the previously quarantined sandbox lifecycle tests. +Full applies its other exact upstream exclusions from +`[full.upstream_test_exclusions]`. Excluded tests do not appear as passes or +skips in the JUnit report. ## Prerequisites @@ -106,6 +117,8 @@ directory and declaring it with a `mod` line in that tier's `mod.rs`. already deployed to it (via Helm or otherwise). - `oc` CLI authenticated to that cluster. - Rust toolchain and `mise` installed. +- Python 3.11 or newer, `cargo-nextest`, and `xsltproc` installed for tiered + runs and HTML reports. - The `openshell` CLI binary built (`cargo build -p openshell-cli`) and its active gateway pointed at the deployed OpenShell instance (`openshell gateway add ...` / `openshell gateway select ...`) — the harness shells out @@ -147,7 +160,7 @@ context you happen to have active elsewhere. This means: | `mise run e2e:odh:tier1` | Tier 1: mapped upstream tests + ODH `tier1::` + image provenance | | `mise run e2e:odh:tier2` | Tier 2: mapped upstream tests + ODH `tier2::` + image provenance | | `mise run e2e:odh:tier3` | Tier 3: mapped upstream tests + ODH `tier3::` + image provenance | -| `cargo test --manifest-path e2e/rust/Cargo.toml --features e2e-odh --test odh -- test_name --exact` | A single ODH test function | +| `cargo nextest run --manifest-path e2e/rust/Cargo.toml --features e2e-odh --test odh -E 'test(=module::test_name)'` | A single ODH test function | Example, running the Smoke tier against a real cluster: @@ -155,16 +168,17 @@ Example, running the Smoke tier against a real cluster: umask 077 oc --kubeconfig ~/.kube/config config view --minify --flatten > kubeconfig chmod 600 kubeconfig -ALLOWED_IMAGE_REGISTRY_PREFIXES="quay.io/opendatahub/,ghcr.io/nvidia/openshell-community/sandboxes/" \ +ALLOWED_IMAGE_REGISTRY_PREFIXES="quay.io/opendatahub/,nvcr.io/nvidia/base/" \ mise run e2e:odh:smoke ``` `ALLOWED_IMAGE_REGISTRY_PREFIXES` is required by the image provenance test — -see below. Set `NAMESPACE`/`RELEASE` too if your deployment doesn't use the -defaults (`openshell`/`openshell`). The Helm chart's -`server.sandboxImagePullPolicy` must also be set to `IfNotPresent` (it -defaults to `""`, i.e. Kubernetes' own default of `Always` for the -`:latest`-tagged sandbox image) — see below. +see below. For RHOAI images, use +`registry.redhat.io/,nvcr.io/nvidia/base/` instead. Set `NAMESPACE`/`RELEASE` +too if your deployment doesn't use the defaults (`openshell`/`openshell`). +The Quay deployment script sets +`sandbox.image.pullPolicy=IfNotPresent`; other deployments must +configure it themselves. ### SELinux-enforcing OCP validation @@ -187,27 +201,20 @@ SELinux-specific proxy fixture. ### Why `e2e:odh` / `e2e:odh:full` run more than you might expect -`e2e-odh` is defined as `e2e-odh = ["e2e-kubernetes"]` in `Cargo.toml`, so it -transitively activates the full upstream feature chain -(`e2e-odh` → `e2e-kubernetes` → `e2e`). Without a `--test` filter, `cargo -test --features e2e-odh` builds and runs **every** test binary whose -`required-features` are satisfied by that chain — not just the `odh` binary. -`e2e:odh` passes `--test odh` specifically to restrict to just the ODH -binary; `e2e:odh:full` intentionally omits that filter to run everything. +`e2e-odh` activates the upstream `e2e-kubernetes` and `e2e` features. +`run-odh-test-tier.sh` selects tests with one nextest filter per tier. +`e2e:odh` selects the `odh` binary; `e2e:odh:full` selects every test binary +enabled by those features. Both produce JUnit XML and HTML in `results/`. ### Every `e2e:odh*` task runs the image provenance check `smoke::image_provenance::test_sandbox_gateway_supervisor_images` is a -regular test in the `odh` binary, so it runs automatically whenever that -binary runs unfiltered: `e2e:odh` (`--test odh`, no substring filter) and -`e2e:odh:full` (no `--test` filter at all) both include it for free, with no -special wrapper needed — a passing test run can't mask a provenance failure, -since it's not a separate step to skip or short-circuit. The tiered tasks -(`e2e:odh:tier1`/`tier2`/`tier3`) go through `run-odh-test-tier.sh`, which -runs it explicitly as an extra step after the tier's own filter, since image -provenance is a property of the deployment, not of any one tier; -`e2e:odh:smoke` doesn't need that extra step since its own `smoke::` filter -already covers it. +regular test in the `odh` binary. The smoke, ODH, and full filters include it. +Tier 1–3 filters include it alongside their assigned tests so one nextest run +and one JUnit report cover the whole tier. `SKIP_IMAGE_PROVENANCE=1` excludes +it from Tier 1–3 when a local deployment cannot meet the image requirements. +An empty tier fails instead of passing with an empty report. Tier 3 currently +has no scenarios, so skipping image provenance makes it fail. ## Image provenance verification @@ -219,7 +226,7 @@ registry, and that no container — including ephemeral containers — has regressed away from `imagePullPolicy: IfNotPresent`. ```bash -ALLOWED_IMAGE_REGISTRY_PREFIXES="quay.io/opendatahub/,ghcr.io/nvidia/openshell-community/sandboxes/" \ +ALLOWED_IMAGE_REGISTRY_PREFIXES="quay.io/opendatahub/,nvcr.io/nvidia/base/" \ cargo test --manifest-path e2e/rust/Cargo.toml --features e2e-odh --test odh \ -- smoke::image_provenance:: ``` @@ -228,46 +235,105 @@ ALLOWED_IMAGE_REGISTRY_PREFIXES="quay.io/opendatahub/,ghcr.io/nvidia/openshell-c must end in `/`) — no default, since an empty list would silently approve any image. This should include every registry prefix your deployment legitimately pulls from: - - `ghcr.io/nvidia/openshell/` — the standard upstream gateway, - supervisor, and CLI image prefix. - - `ghcr.io/nvidia/openshell-community/sandboxes/` — the sandbox default - image (`server.sandboxImage` in the Helm chart). There is no - downstream-built sandbox base image yet (only `gateway`, `supervisor`, - and `cli` have Tekton pipelines under `.tekton/`), so this upstream - prefix has to stay allowed until one exists. This is a different path - alongside the upstream gateway and CLI images. + - `quay.io/opendatahub/` — the Quay deployment script's default repository + namespace for the midstream gateway, supervisor, and sandbox runtime + images. + - `registry.redhat.io/` — RHOAI component images. + - `nvcr.io/nvidia/base/` — the Helm chart's current default workload + image (`server.sandboxImage`). Include the registry prefix for any + other workload image selected by the deployment. A prefix without a trailing `/` is rejected outright, since it could otherwise match a lookalike host (e.g. `registry.redhat.io` would also match `registry.redhat.io.attacker.example/image`). - `NAMESPACE`/`RELEASE` env vars default to `openshell`/`openshell`. - The check is a registry-prefix allowlist, not an exact image/digest match. - The allowlist is explicit about both the upstream gateway images and the - upstream sandbox image. + It checks each observed image against the configured allowed prefixes. - The `imagePullPolicy: IfNotPresent` check applies to every container, - including the sandbox pod's. The Helm chart's `server.sandboxImagePullPolicy` - defaults to `""` (Kubernetes' own default, which is `Always` for a - `:latest`-tagged image like the default sandbox image) — deployments must - set it explicitly, e.g. `--set server.sandboxImagePullPolicy=IfNotPresent`, - or this check fails on a freshly installed chart. + including the sandbox pod's. The Quay deployment script sets + `sandbox.image.pullPolicy=IfNotPresent`; other deployments must set it + explicitly. - Requires the `oc` CLI in PATH with a kubeconfig targeting the cluster (same as the rest of this suite). When `OPENSHELL_E2E_KUBE_CONTEXT_ACTIVE` is set, all provenance queries use that context; otherwise they use the kubeconfig's current context. -- Skip it locally with `SKIP_IMAGE_PROVENANCE=1 mise run e2e:odh:tier1` (e.g. - if `oc` isn't configured for the target cluster in your current shell) — - this only affects the tiered tasks' extra step; `e2e:odh`/`e2e:odh:full`/ - `e2e:odh:smoke` always include it since it's just another test in scope. +- Skip it locally with `SKIP_IMAGE_PROVENANCE=1 mise run e2e:odh:tier1` when + testing a local deployment. Smoke, ODH, and full always include it. + +## E2E container image + +The Konflux `odh-openshell-e2e` image builds the OpenShell CLI and the +`e2e-odh` nextest archive from this checkout. It includes `cargo`, +`cargo-nextest`, `oc`, `kubectl`, Helm, Python 3.11, `xsltproc`, Git, and the SSH +client used by sandbox lifecycle tests. +See [the ODH image build overview](../../../../deploy/konflux/e2e-odh/README.md) +for its build and runtime boundaries. +The image is built for x86_64 and aarch64 from pinned Cargo, RPM, and generic +artifacts. On Linux with Hermeto, `rpm`, and Podman installed, build it +locally with: + +```shell +./deploy/konflux/build-local.sh e2e-odh +``` + +The local build tags the image as `odh-openshell-e2e`. + +The image accepts `smoke`, `tier1`, `tier2`, `tier3`, `odh`, or `full` +as its first argument; `smoke` is the default. The Shift-Left job mounts +cluster credentials and a writable report directory, then passes environment +variables through `--env-file`: + +```shell +# Login to the cluster storing the kubeconfig file in the current folder +oc login --token=XXXXXX \ +--server=https://api.example.com:443 \ +--kubeconfig $PWD/kubeconfig + +# Run the e2e tests +podman run --rm \ + -v /path/to/reports:/home/odh/openshell-e2e-odh/results:Z,U \ + -v /path/to/kubeconfig:/home/odh/openshell-e2e-odh/.kube/config:ro,Z \ + --env-file containerEnvFile \ + quay.io/opendatahub/odh-openshell-e2e:odh-stable smoke +``` + +By default, the entrypoint calls +`odh/scripts/openshell-deploy-from-quay.sh deploy --yes`, runs the tier, +then calls `teardown --yes` even if tests fail. The deploy script refuses to +replace an existing namespace unless +`OPENSHELL_E2E_REPLACE_EXISTING=1` is set and the namespace has the +`openshell.nvidia.com/deployed-by=odh-e2e` label. Failed deployments are +cleaned up after namespace creation; validation failures preserve local +gateway registration. SIGTERM is forwarded to nextest before teardown. +The kubeconfig must permit +namespace creation, Helm deployment, and the OpenShift operations used by +the tests. Set `OPENSHELL_E2E_DEPLOY_GATEWAY=0` only when a gateway is +already deployed and configured for the CLI inside the container. + +The environment file must set `IMAGE_TAG` for the gateway, supervisor, +and sandbox images, plus `ALLOWED_IMAGE_REGISTRY_PREFIXES` for the +provenance test. By default, the deployment uses the +`quay.io/opendatahub/odh-openshell-*` midstream repositories. +Override `QUAY_NAMESPACE`, `GATEWAY_IMAGE`, `SUPERVISOR_IMAGE`, or +`SANDBOX_IMAGE` when testing another repository. `NAMESPACE`, `RELEASE`, +`ROUTE_HOST`, and `GATEWAY_NAME` control the deployment. The image writes +`e2e-odh-.xml` and +`e2e-odh-.html` to `results/`. Set +`OPENSHELL_E2E_REPORT_NAME` to choose another basename. The JUnit report +is produced by one nextest invocation per tier. + +Konflux automation creates the Tekton YAML files for the e2e image. ## Rebase guidance -- **Fork-only, no conflict risk:** everything in this directory - (`e2e/rust/tests/odh/`), plus `tasks/test-odh.toml`. -- **Touches upstream:** only `e2e/rust/Cargo.toml`, and only via two - appended blocks (the `e2e-odh` feature line, and the `[[test]]` entry for - the `odh` binary). If upstream changes this file and a rebase conflicts, - resolution is mechanical: re-append both blocks at the end of their - respective sections. +- **Fork-only paths:** this directory (`e2e/rust/tests/odh/`), + `tasks/test-odh.toml`, the new e2e image files under + `deploy/konflux/e2e-odh/`, `deploy/docker/`, and `odh/scripts/`. +- **Shared files:** the image adds to `.config/nextest.toml`, + `deploy/konflux/build-local.sh`, and `.dockerignore`. Review these edits + whenever upstream changes those files. The fork also carries the + `e2e-odh` feature and `odh` test entry in `e2e/rust/Cargo.toml`; preserve + those entries when syncing upstream changes to that file. - **Shared harness dependency:** ODH tests use the upstream test harness library (`e2e/rust/src/`, e.g. `openshell_e2e::harness::binary::openshell_cmd`, `openshell_e2e::harness::sandbox::SandboxGuard`). If upstream changes those diff --git a/e2e/rust/tests/odh/run-odh-test-tier.sh b/e2e/rust/tests/odh/run-odh-test-tier.sh index ff12a4b409..d767c6825a 100755 --- a/e2e/rust/tests/odh/run-odh-test-tier.sh +++ b/e2e/rust/tests/odh/run-odh-test-tier.sh @@ -1,72 +1,153 @@ #!/usr/bin/env bash # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 +# +# Run one ODH test tier against an already deployed gateway. The image +# entrypoint handles gateway setup and teardown; this script only runs tests +# and writes a JUnit XML report with an HTML companion. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT="$(cd "${SCRIPT_DIR}/../../../.." && pwd)" TIERS_FILE="${SCRIPT_DIR}/tiers.toml" +PYTHON_BIN="${PYTHON_BIN:-python3}" TIER="${1:-}" -log() { echo "==> $*"; } -fail() { echo "ERROR: $*" >&2; exit 1; } - -if [ -z "${TIER}" ]; then - fail "usage: $0 . Available tiers: $(python3 -c ' -import sys, tomllib -with open(sys.argv[1], "rb") as f: - print(" ".join(sorted(tomllib.load(f)))) -' "$TIERS_FILE")" +if [[ -z "${TIER}" || $# -ne 1 ]]; then + echo "Usage: $0 " >&2 + exit 2 fi -tier_config="$(python3 - "${TIERS_FILE}" "${TIER}" <<'PY' +# Build one nextest filter for the selected tier. Running every selected test +# in one nextest invocation gives Jenkins a complete report for the tier. +filter="$("${PYTHON_BIN}" - "${TIERS_FILE}" "${TIER}" "${SKIP_IMAGE_PROVENANCE:-0}" <<'PY' +import re import sys import tomllib -with open(sys.argv[1], "rb") as f: - tiers = tomllib.load(f) +with open(sys.argv[1], "rb") as stream: + tiers = tomllib.load(stream) tier = sys.argv[2] if tier not in tiers: - print(f"unknown tier {tier!r}", file=sys.stderr) - sys.exit(1) + sys.exit(f"unknown tier {tier!r}; available: {', '.join(sorted(tiers))}") + +config = tiers[tier] +exclusions = config.get("upstream_test_exclusions", {}) +for binary, tests in exclusions.items(): + if not re.fullmatch(r"[A-Za-z0-9_-]+", binary): + sys.exit(f"invalid upstream test binary: {binary!r}") + for test in tests: + if not re.fullmatch(r"[A-Za-z0-9_:]+", test): + sys.exit(f"invalid excluded upstream test: {test!r}") + +if config.get("odh_only"): + print("binary(=odh)") +elif config.get("all_binaries"): + excluded = " | ".join( + f"(binary(={binary}) & test(={test}))" + for binary, tests in exclusions.items() + for test in tests + ) + print(f"(all() - ({excluded}))" if excluded else "all()") +else: + terms = [] + odh_filter = config.get("odh_filter", "") + if odh_filter: + if not re.fullmatch(r"[A-Za-z0-9_:]+", odh_filter): + sys.exit(f"invalid ODH test filter: {odh_filter!r}") + terms.append(f"(binary(=odh) & test(~{odh_filter}))") + + provenance = "smoke::image_provenance::" + if sys.argv[3] != "1" and not (odh_filter and provenance.startswith(odh_filter)): + terms.append(f"(binary(=odh) & test(~{provenance}))") + + upstream_tests = config.get("upstream_tests", []) + unknown_binaries = exclusions.keys() - set(upstream_tests) + if unknown_binaries: + sys.exit(f"excluded tests reference unselected binaries: {sorted(unknown_binaries)}") + + for binary in upstream_tests: + if not re.fullmatch(r"[A-Za-z0-9_-]+", binary): + sys.exit(f"invalid upstream test binary: {binary!r}") + selection = f"binary(={binary})" + for test in exclusions.get(binary, []): + selection = f"({selection} - test(={test}))" + terms.append(selection) -cfg = tiers[tier] -print(" ".join(cfg.get("upstream_tests", []))) -print(cfg.get("odh_filter", "")) + if not terms: + sys.exit(f"tier {tier!r} selects no tests") + print(" | ".join(terms)) PY )" -readarray -t tier_config_lines <<< "${tier_config}" -read -ra UPSTREAM_TESTS <<< "${tier_config_lines[0]:-}" -ODH_FILTER="${tier_config_lines[1]:-}" - -overall_status=0 - -for test_bin in "${UPSTREAM_TESTS[@]:-}"; do - [ -n "$test_bin" ] || continue - log "Running upstream test binary: $test_bin" - if ! cargo test --manifest-path "${ROOT}/e2e/rust/Cargo.toml" \ - --features e2e-odh --test "$test_bin" -- --nocapture; then - overall_status=1 - fi -done - -if [ -n "${ODH_FILTER}" ]; then - log "Running ODH tests matching: $ODH_FILTER" - cargo test --manifest-path "${ROOT}/e2e/rust/Cargo.toml" \ - --features e2e-odh --test odh -- "$ODH_FILTER" --nocapture || overall_status=1 +name="${OPENSHELL_E2E_REPORT_NAME:-e2e-odh-${TIER}}" +if [[ ! "${name}" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then + echo "ERROR: invalid report name: ${name}" >&2 + exit 2 fi -if [ "${SKIP_IMAGE_PROVENANCE:-0}" = "1" ]; then - log "Skipping image provenance check (SKIP_IMAGE_PROVENANCE=1)" -elif [[ "smoke::image_provenance::" == "${ODH_FILTER}"* ]]; then - log "Image provenance already covered by tier filter: $ODH_FILTER" +mkdir -p "${ROOT}/results" +junit_xml="${ROOT}/results/e2e-odh.xml" +report="${ROOT}/results/${name}.xml" +rm -f "${junit_xml}" "${report}" "${report%.xml}.html" + +nextest_args=( + --profile e2e-odh + --config-file "${ROOT}/.config/nextest.toml" + --no-tests fail + -E "${filter}" +) +if [[ -n "${OPENSHELL_E2E_NEXTEST_ARCHIVE:-}" ]]; then + nextest_args+=( + --archive-file "${OPENSHELL_E2E_NEXTEST_ARCHIVE}" + --workspace-remap "${ROOT}/e2e/rust" + ) +else + nextest_args+=( + --manifest-path "${ROOT}/e2e/rust/Cargo.toml" + --target-dir "${ROOT}/e2e/rust/target" + --features e2e-odh + ) +fi + +echo "==> Running ODH tier ${TIER}: ${filter}" +status=0 +nextest_pid="" +stop_nextest() { + local signal="$1" code="$2" + trap '' INT TERM + if [[ -n "${nextest_pid}" ]]; then + kill -"${signal}" "${nextest_pid}" 2>/dev/null || true + wait "${nextest_pid}" || true + fi + exit "${code}" +} +trap 'stop_nextest TERM 130' INT +trap 'stop_nextest TERM 143' TERM +cargo nextest run "${nextest_args[@]}" & +nextest_pid=$! +wait "${nextest_pid}" || status=$? +nextest_pid="" +trap - INT TERM + +if [[ -f "${junit_xml}" ]]; then + if [[ "${report}" != "${junit_xml}" ]]; then + mv -f "${junit_xml}" "${report}" + fi + echo "JUnit report: ${report}" + if command -v xsltproc >/dev/null 2>&1; then + xsltproc --stringparam title "${name}" \ + "${ROOT}/scripts/junit-to-html.xsl" "${report}" \ + > "${report%.xml}.html" \ + || echo "WARNING: failed to render HTML report" >&2 + else + echo "WARNING: xsltproc not found; HTML report unavailable" >&2 + fi else - log "Verifying image provenance" - cargo test --manifest-path "${ROOT}/e2e/rust/Cargo.toml" \ - --features e2e-odh --test odh -- smoke::image_provenance:: --nocapture || overall_status=1 + echo "ERROR: cargo-nextest did not write a JUnit report" >&2 + [[ "${status}" != 0 ]] || status=1 fi -exit "$overall_status" +exit "${status}" diff --git a/e2e/rust/tests/odh/tiers.toml b/e2e/rust/tests/odh/tiers.toml index 222d770519..4784241bc0 100644 --- a/e2e/rust/tests/odh/tiers.toml +++ b/e2e/rust/tests/odh/tiers.toml @@ -4,9 +4,9 @@ # Maps each test tier to the upstream e2e test binaries and the ODH test # module filter that belong to it. Read at runtime by run-odh-test-tier.sh. # -# `upstream_tests` names `[[test]]` entries from e2e/rust/Cargo.toml (run via -# `cargo test --test `). `odh_filter` is a `cargo test` substring filter -# applied to the `odh` test binary, matching module-path-prefixed test names +# `upstream_tests` names explicit or auto-discovered Cargo test binaries. +# `upstream_test_exclusions` removes named tests from a selected binary. +# `odh_filter` matches module-path-prefixed test names in the `odh` binary # (e.g. "smoke::" runs every test under tests/odh/smoke/). # # The upstream test assignments below are placeholders — fill them in based @@ -26,15 +26,35 @@ odh_filter = "smoke::" [tier1] description = "High-priority tests excluding Smoke (15 min target)" -upstream_tests = ["readyz_health", "sandbox_lifecycle", "landlock", "user_namespaces"] +upstream_tests = ["sandbox_lifecycle", "landlock", "user_namespaces"] odh_filter = "tier1::" [tier2] description = "Medium/low priority positive tests" -upstream_tests = ["kubernetes_corporate_proxy", "workspace_lifecycle"] +# Corporate proxy needs a cluster-reachable fixture configured before Helm install. +upstream_tests = ["workspace_lifecycle"] odh_filter = "tier2::" [tier3] description = "Negative and destructive tests" upstream_tests = [] odh_filter = "tier3::" + +[odh] +description = "All ODH-specific tests" +upstream_tests = [] +odh_filter = "" +odh_only = true + +[full] +description = "All feature-enabled upstream and ODH tests except listed exclusions" +upstream_tests = [] +odh_filter = "" +all_binaries = true + +[full.upstream_test_exclusions] +# Full OpenShift runs identified the failures and timeouts below. +# Keep each exclusion exact so other tests in these binaries still run. +live_policy_update = ["initial_sparse_policy_is_acknowledged_as_loaded"] +no_proxy = ["sandbox_reaches_localhost_without_proxy_environment"] +port_forward = ["port_forward_echo"] diff --git a/odh/scripts/e2e-odh-entrypoint.sh b/odh/scripts/e2e-odh-entrypoint.sh new file mode 100755 index 0000000000..e92bacfc9d --- /dev/null +++ b/odh/scripts/e2e-odh-entrypoint.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Container entrypoint for the Shift-Left ODH e2e image. Deploy the selected +# Quay images, run one test tier, and remove that deployment on every exit. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +DEPLOY_SCRIPT="${SCRIPT_DIR}/openshell-deploy-from-quay.sh" +TEST_SCRIPT="${ROOT}/e2e/rust/tests/odh/run-odh-test-tier.sh" +TIER="${1:-smoke}" + +if [[ $# -gt 1 ]]; then + echo "Usage: $0 [smoke|tier1|tier2|tier3|odh|full]" >&2 + exit 2 +fi +case "${TIER}" in + smoke|tier1|tier2|tier3|odh|full) ;; + *) echo "ERROR: unknown test tier: ${TIER}" >&2; exit 2 ;; +esac + +if [[ -z "${KUBECONFIG:-}" ]]; then + KUBECONFIG="${ROOT}/.kube/config" + export KUBECONFIG +fi +if [[ ! -f "${KUBECONFIG}" ]]; then + echo "ERROR: kubeconfig not found at ${KUBECONFIG}" >&2 + exit 1 +fi + +cleanup_enabled=0 +child_pid="" +deploy_state_dir="" +cleanup() { + local status=$? + local cleanup_status=0 + trap - EXIT + if [[ "${cleanup_enabled}" == 1 && -f "${deploy_state_dir}/namespace" ]]; then + teardown_args=(teardown --yes) + if [[ ! -f "${deploy_state_dir}/gateway" ]]; then + teardown_args+=(--keep-local-gateway) + fi + "${DEPLOY_SCRIPT}" "${teardown_args[@]}" || cleanup_status=$? + if [[ "${cleanup_status}" != 0 ]]; then + echo "ERROR: OpenShell teardown failed (${cleanup_status})" >&2 + [[ "${status}" != 0 ]] || status="${cleanup_status}" + fi + fi + if [[ -n "${deploy_state_dir}" ]]; then + rm -rf "${deploy_state_dir}" + fi + exit "${status}" +} +stop_child() { + local signal="$1" code="$2" + trap '' INT TERM + if [[ -n "${child_pid}" ]]; then + kill -"${signal}" "${child_pid}" 2>/dev/null || true + wait "${child_pid}" || true + fi + exit "${code}" +} +run_child() { + local status=0 + "$@" & + child_pid=$! + wait "${child_pid}" || status=$? + child_pid="" + return "${status}" +} + +trap cleanup EXIT +# Bash background jobs inherit SIGINT ignored; use TERM to stop the child. +trap 'stop_child TERM 130' INT +trap 'stop_child TERM 143' TERM + +if [[ "${OPENSHELL_E2E_DEPLOY_GATEWAY:-1}" != 0 ]]; then + deploy_args=(deploy --yes) + if [[ "${OPENSHELL_E2E_REPLACE_EXISTING:-0}" == 1 ]]; then + deploy_args+=(--replace-existing) + fi + deploy_state_dir="$(mktemp -d)" + export OPENSHELL_E2E_DEPLOY_STATE_DIR="${deploy_state_dir}" + cleanup_enabled=1 + run_child "${DEPLOY_SCRIPT}" "${deploy_args[@]}" + echo ">> Importing example provider profiles" + run_child "${OPENSHELL_BIN:-openshell}" provider profile import --from "${ROOT}/providers" --global +fi + +run_child "${TEST_SCRIPT}" "${TIER}" diff --git a/odh/scripts/openshell-deploy-from-quay.sh b/odh/scripts/openshell-deploy-from-quay.sh new file mode 100755 index 0000000000..a35246b8e7 --- /dev/null +++ b/odh/scripts/openshell-deploy-from-quay.sh @@ -0,0 +1,301 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Deploy OpenShell to the currently logged-in OpenShift cluster using images +# pushed to Quay, or remove a deployment created by this script. +# +# Usage: +# ./openshell-deploy-from-quay.sh [deploy] [--yes] [--replace-existing] +# ./openshell-deploy-from-quay.sh teardown [--yes] +# +# The e2e image entrypoint can run `deploy --yes`, run the tests, then call +# `teardown --yes` from an exit trap. A noninteractive deployment refuses to +# replace an existing namespace unless --replace-existing is also supplied. +# +# Prerequisites: +# - oc logged in to the target OpenShift cluster with cluster-admin access +# - helm and openshell installed +# - Images already pushed to +# quay.io/opendatahub/odh-openshell-{gateway,supervisor,sandbox}: +# +# Env overrides: +# IMAGE_TAG image tag to deploy (required without Git branch metadata) +# QUAY_NAMESPACE quay.io namespace (default: opendatahub) +# GATEWAY_IMAGE full gateway image repository override +# GATEWAY_IMAGE_DIGEST optional gateway digest, overriding IMAGE_TAG for Helm +# SUPERVISOR_IMAGE full supervisor image repository override +# SANDBOX_IMAGE full sandbox image repository override +# NAMESPACE target k8s namespace (default: openshell) +# RELEASE Helm release name (default: openshell) +# ROUTE_HOST gateway Route hostname (default: openshell.) +# GATEWAY_NAME local CLI gateway name (default: openshift) + +set -euo pipefail + +usage() { + cat <<'USAGE' +Usage: openshell-deploy-from-quay.sh [deploy|teardown] [--yes] [--replace-existing] [--keep-local-gateway] + + deploy Deploy OpenShell (default). Prompts before changing the cluster. + teardown Remove the Helm release, namespace, and local gateway. + --yes Skip the confirmation prompt for automation. + --replace-existing Permit --yes to replace an existing namespace during deploy. + --keep-local-gateway Preserve local CLI registration during teardown. +USAGE +} + +fail() { + echo "ERROR: $*" >&2 + exit 1 +} + +ACTION=deploy +ASSUME_YES=0 +REPLACE_EXISTING=0 +KEEP_LOCAL_GATEWAY=0 + +if [[ "${1:-}" == deploy || "${1:-}" == teardown ]]; then + ACTION="$1" + shift +fi + +while (( $# > 0 )); do + case "$1" in + --yes) ASSUME_YES=1 ;; + --replace-existing) REPLACE_EXISTING=1 ;; + --keep-local-gateway) KEEP_LOCAL_GATEWAY=1 ;; + -h|--help) usage; exit 0 ;; + *) usage >&2; fail "unknown argument: $1" ;; + esac + shift +done + +if [[ "${ACTION}" == teardown && "${REPLACE_EXISTING}" == 1 ]]; then + fail "--replace-existing only applies to deploy" +fi +if [[ "${ACTION}" != teardown && "${KEEP_LOCAL_GATEWAY}" == 1 ]]; then + fail "--keep-local-gateway only applies to teardown" +fi + +confirm_action() { + local response + if [[ "${ASSUME_YES}" == 1 ]]; then + return 0 + fi + read -r -p "$1 [y/N] " response + [[ "${response}" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 1; } +} + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +CHART="${REPO_ROOT}/deploy/helm/openshell" + +QUAY_NAMESPACE="${QUAY_NAMESPACE:-opendatahub}" +NAMESPACE="${NAMESPACE:-openshell}" +RELEASE="${RELEASE:-openshell}" +GATEWAY_NAME="${GATEWAY_NAME:-openshift}" +QNS="quay.io/${QUAY_NAMESPACE}" +DEPLOY_LABEL="openshell.nvidia.com/deployed-by" +DEPLOY_LABEL_VALUE="odh-e2e" +if [[ "${RELEASE}" == *openshell* ]]; then + HELM_FULLNAME="${RELEASE:0:63}" +else + HELM_FULLNAME="${RELEASE}-openshell" + HELM_FULLNAME="${HELM_FULLNAME:0:63}" +fi +HELM_FULLNAME="${HELM_FULLNAME%-}" + +for tool in oc helm openshell; do + command -v "${tool}" >/dev/null 2>&1 || fail "${tool} is required" +done +if [[ "${ACTION}" == deploy ]]; then + command -v base64 >/dev/null 2>&1 || fail "base64 is required" + [[ -d "${CHART}" ]] || fail "Helm chart not found at ${CHART}" +fi + +echo ">> Verifying oc session" +oc whoami >/dev/null + +if [[ "${ACTION}" == deploy ]]; then + if ! oc api-resources --api-group=route.openshift.io 2>/dev/null | grep -q routes; then + fail "route.openshift.io not found; this does not look like an OpenShift cluster" + fi +fi + +if [[ "${ACTION}" == teardown ]]; then + teardown_status=0 + namespace_resource="$(oc get namespace "${NAMESPACE}" --ignore-not-found -o name)" + if [[ -n "${namespace_resource}" ]]; then + owner="$(oc get namespace "${NAMESPACE}" \ + -o go-template='{{index .metadata.labels "openshell.nvidia.com/deployed-by"}}')" + if [[ "${owner}" != "${DEPLOY_LABEL_VALUE}" ]]; then + fail "namespace ${NAMESPACE} was not created by this script; refusing to delete it" + fi + confirm_action "Remove OpenShell release '${RELEASE}' and namespace '${NAMESPACE}'?" + echo ">> Uninstalling Helm release '${RELEASE}'" + helm uninstall "${RELEASE}" --namespace "${NAMESPACE}" --wait 2>/dev/null || true + echo ">> Deleting namespace '${NAMESPACE}'" + oc delete namespace "${NAMESPACE}" --wait || teardown_status=$? + else + if [[ "${KEEP_LOCAL_GATEWAY}" != 1 ]]; then + confirm_action "Remove local gateway '${GATEWAY_NAME}'?" + fi + echo ">> Namespace '${NAMESPACE}' is already absent" + fi + if [[ "${KEEP_LOCAL_GATEWAY}" != 1 ]]; then + echo ">> Removing local gateway '${GATEWAY_NAME}'" + openshell gateway remove "${GATEWAY_NAME}" 2>/dev/null || true + fi + if [[ "${teardown_status}" != 0 ]]; then + fail "namespace ${NAMESPACE} could not be removed" + fi + echo ">> Teardown complete" + exit 0 +fi + +if [[ -z "${IMAGE_TAG:-}" ]]; then + if ! branch="$(git -C "${REPO_ROOT}" symbolic-ref --quiet --short HEAD)"; then + fail "IMAGE_TAG is required when the source tree has no Git branch metadata" + fi + IMAGE_TAG="${branch//\//-}" + IMAGE_TAG="${IMAGE_TAG//[^a-zA-Z0-9._-]/}" + IMAGE_TAG="${IMAGE_TAG:0:128}" +fi + +GATEWAY_IMAGE="${GATEWAY_IMAGE:-${QNS}/odh-openshell-gateway}" +SUPERVISOR_IMAGE="${SUPERVISOR_IMAGE:-${QNS}/odh-openshell-supervisor}" +SANDBOX_IMAGE="${SANDBOX_IMAGE:-${QNS}/odh-openshell-sandbox}" +for image in "${GATEWAY_IMAGE}" "${SUPERVISOR_IMAGE}" "${SANDBOX_IMAGE}"; do + [[ "${image}" == */* ]] || fail "image repository must include a registry: ${image}" +done +GATEWAY_REGISTRY="${GATEWAY_IMAGE%%/*}" +GATEWAY_REPOSITORY="${GATEWAY_IMAGE#*/}" +SUPERVISOR_REGISTRY="${SUPERVISOR_IMAGE%%/*}" +SUPERVISOR_REPOSITORY="${SUPERVISOR_IMAGE#*/}" +SANDBOX_REGISTRY="${SANDBOX_IMAGE%%/*}" +SANDBOX_REPOSITORY="${SANDBOX_IMAGE#*/}" +IMAGE_DIGEST_ARGS=() +GATEWAY_IMAGE_REF="${GATEWAY_IMAGE}:${IMAGE_TAG}" +if [[ -n "${GATEWAY_IMAGE_DIGEST:-}" ]]; then + [[ "${GATEWAY_IMAGE_DIGEST}" == sha256:* ]] || fail "GATEWAY_IMAGE_DIGEST must start with sha256:" + IMAGE_DIGEST_ARGS+=(--set-string "gateway.image.digest=${GATEWAY_IMAGE_DIGEST}") + GATEWAY_IMAGE_REF="${GATEWAY_IMAGE}@${GATEWAY_IMAGE_DIGEST}" +fi + +if [[ -z "${ROUTE_HOST:-}" ]]; then + APPS="$(oc get ingresses.config/cluster -o jsonpath='{.spec.domain}')" + ROUTE_HOST="openshell.${APPS}" +fi + +echo +echo "=====================================================" +echo " Deploy OpenShell on OpenShift using custom images" +echo "=====================================================" +echo " Cluster: $(oc whoami --show-server)" +echo " User: $(oc whoami)" +echo " Namespace: ${NAMESPACE}" +echo " Helm release: ${RELEASE}" +echo " Gateway name: ${GATEWAY_NAME}" +echo " Route host: ${ROUTE_HOST}" +echo " Images:" +echo " - ${GATEWAY_IMAGE_REF}" +echo " - ${SUPERVISOR_IMAGE}:${IMAGE_TAG}" +echo " - ${SANDBOX_IMAGE}:${IMAGE_TAG}" +echo " Actions:" +echo " 1. Replace namespace '${NAMESPACE}' if it exists" +echo " 2. Create namespace" +echo " 3. Deploy Helm release '${RELEASE}' and wait for the gateway" +echo " 4. Register local gateway '${GATEWAY_NAME}'" +echo "=====================================================" +echo + +namespace_resource="$(oc get namespace "${NAMESPACE}" --ignore-not-found -o name)" +if [[ -n "${namespace_resource}" ]]; then + owner="$(oc get namespace "${NAMESPACE}" \ + -o go-template='{{index .metadata.labels "openshell.nvidia.com/deployed-by"}}')" + [[ "${owner}" == "${DEPLOY_LABEL_VALUE}" ]] || + fail "namespace ${NAMESPACE} was not created by this script; refusing to replace it" +fi +if [[ -n "${namespace_resource}" ]] && + [[ "${ASSUME_YES}" == 1 && "${REPLACE_EXISTING}" != 1 ]]; then + fail "namespace ${NAMESPACE} already exists; pass --replace-existing to replace it noninteractively" +fi + +confirm_action "Proceed with deploy?" + +if [[ -n "${namespace_resource}" ]]; then + echo ">> Uninstalling Helm release '${RELEASE}' (if present)" + helm uninstall "${RELEASE}" --namespace "${NAMESPACE}" --wait 2>/dev/null || true + echo ">> Deleting namespace '${NAMESPACE}'" + oc delete namespace "${NAMESPACE}" --wait +fi + +echo ">> Removing local gateway '${GATEWAY_NAME}' (if present)" +openshell gateway remove "${GATEWAY_NAME}" 2>/dev/null || true + +echo ">> Creating namespace '${NAMESPACE}'" +# Apply the ownership label in the create request, so a failed label update +# cannot leave behind a namespace that teardown refuses to remove. +if [[ -n "${OPENSHELL_E2E_DEPLOY_STATE_DIR:-}" ]]; then + : > "${OPENSHELL_E2E_DEPLOY_STATE_DIR}/namespace" +fi +oc create namespace "${NAMESPACE}" --dry-run=client -o yaml | + oc label --local -f - "${DEPLOY_LABEL}=${DEPLOY_LABEL_VALUE}" -o yaml | + oc create -f - + +echo ">> Deploying OpenShell via Helm" +helm upgrade --install "${RELEASE}" "${CHART}" \ + --namespace "${NAMESPACE}" --create-namespace \ + --set-string "gateway.image.registry=${GATEWAY_REGISTRY}" \ + --set-string "gateway.image.repository=${GATEWAY_REPOSITORY}" \ + --set-string "gateway.image.tag=${IMAGE_TAG}" \ + --set-string "supervisor.image.registry=${SUPERVISOR_REGISTRY}" \ + --set-string "supervisor.image.repository=${SUPERVISOR_REPOSITORY}" \ + --set-string "supervisor.image.tag=${IMAGE_TAG}" \ + --set-string "sandboxRuntime.image.registry=${SANDBOX_REGISTRY}" \ + --set-string "sandboxRuntime.image.repository=${SANDBOX_REPOSITORY}" \ + --set-string "sandboxRuntime.image.tag=${IMAGE_TAG}" \ + --set "sandbox.image.pullPolicy=IfNotPresent" \ + --set "podSecurityContext.fsGroup=null" \ + --set "securityContext.runAsUser=null" \ + --set "openshiftRoute.enabled=true" \ + --set "openshiftRoute.host=${ROUTE_HOST}" \ + --set "pkiInitJob.serverDnsNames[0]=${ROUTE_HOST}" \ + --set "server.auth.allowUnauthenticatedUsers=true" \ + "${IMAGE_DIGEST_ARGS[@]}" + +echo ">> Waiting for gateway rollout" +if ! oc -n "${NAMESPACE}" rollout status "statefulset/${HELM_FULLNAME}" --timeout=300s; then + oc -n "${NAMESPACE}" get pods + fail "gateway did not become ready" +fi + +# Extract client mTLS materials so the CLI can reach the mandatory-mTLS gateway. +MTLS_DIR="${HOME}/.config/openshell/gateways/${GATEWAY_NAME}/mtls" +echo ">> Writing client mTLS materials to ${MTLS_DIR}" +umask 077 +mkdir -p "${MTLS_DIR}" +chmod 0700 "${MTLS_DIR}" +for mtls_file in ca.crt tls.crt tls.key; do + : > "${MTLS_DIR}/${mtls_file}" + chmod 0600 "${MTLS_DIR}/${mtls_file}" +done +oc -n "${NAMESPACE}" get secret openshell-client-tls \ + -o jsonpath='{.data.ca\.crt}' | base64 -d > "${MTLS_DIR}/ca.crt" +oc -n "${NAMESPACE}" get secret openshell-client-tls \ + -o jsonpath='{.data.tls\.crt}' | base64 -d > "${MTLS_DIR}/tls.crt" +oc -n "${NAMESPACE}" get secret openshell-client-tls \ + -o jsonpath='{.data.tls\.key}' | base64 -d > "${MTLS_DIR}/tls.key" + +echo ">> Registering gateway '${GATEWAY_NAME}' in the local CLI" +if [[ -n "${OPENSHELL_E2E_DEPLOY_STATE_DIR:-}" ]]; then + : > "${OPENSHELL_E2E_DEPLOY_STATE_DIR}/gateway" +fi +openshell gateway add "https://${ROUTE_HOST}" --local --name "${GATEWAY_NAME}" + +echo +echo ">> Done. Verify with:" +echo " oc -n ${NAMESPACE} get pods,route" +echo " openshell gateway select ${GATEWAY_NAME}" +echo " openshell status" diff --git a/skills/debug-openshell-cluster/SKILL.md b/skills/debug-openshell-cluster/SKILL.md index c8b763e399..6ea9dfd6dd 100644 --- a/skills/debug-openshell-cluster/SKILL.md +++ b/skills/debug-openshell-cluster/SKILL.md @@ -954,7 +954,7 @@ credential failures. | Kubernetes sandbox pod stuck pending, workspace PVC unbound | Cluster has no default `StorageClass` and OpenShell does not set `storageClassName` on the workspace PVC (clusters with a default `StorageClass` bind fine without it) | `kubectl -n openshell describe pvc`; set `server.workspaceStorageClass` (gateway config `workspace_storage_class`) to a valid `StorageClass` | | Kubernetes gateway pod crash loops | Missing secret, bad DB URL, bad TLS config | `kubectl -n openshell logs deployment/openshell -c openshell-gateway` or `kubectl -n openshell logs statefulset/openshell -c openshell-gateway` | | OpenShift gateway pod fails to start with an SCC/`runAsUser` error (e.g. `unable to validate against any security context constraint`) | Chart's default `podSecurityContext`/`securityContext` hardcodes `runAsUser`/`fsGroup`, which the restricted-v2 SCC rejects; it must instead inject the namespace-assigned UID/GID range | `oc -n openshell describe pod `; deploy with `podSecurityContext: null` and clear `securityContext.runAsUser` (see `deploy/helm/openshell/ci/values-openshift-scc.yaml`) | -| OpenShift sandbox pod fails to start (`unable to validate against any security context constraint`) | The `openshell-sandbox` service account lacks the privileged SCC it needs | `oc adm policy add-scc-to-user privileged -z openshell-sandbox -n openshell`; remove with `remove-scc-from-user` when done | +| OpenShift sandbox pod fails to start (`unable to validate against any security context constraint`) | The pod's security context does not satisfy the namespace's SCC constraints | Inspect pod events and namespace-assigned UID/GID ranges. The current sandbox runtime runs without privileges and does not require a privileged SCC grant. | | OpenShift self-hosted Vault/OpenBao credential store pod never schedules (waits time out with `no matching resources found`) | The store's Helm chart pins `runAsUser`/`fsGroup`/seccomp, which restricted-v2 rejects, so the StatefulSet controller never creates the pod | Deploy the store's chart in its OpenShift mode (`--set global.openshift=true` for the OpenBao/Vault chart) so the namespace SCC assigns a compliant security context — no manual SCC grant needed | | Vault credential driver returns HTTP 403 / `Vault Kubernetes auth denied the configured role` on provider create | Vault's `auth/kubernetes` method or the gateway login role is not provisioned, or the role is not bound to the gateway service account and namespace | In Vault: `bao auth enable kubernetes` and `bao write auth/kubernetes/config kubernetes_host=... kubernetes_ca_cert=@...`; ensure the login role's `bound_service_account_names`/`bound_service_account_namespaces` match the gateway SA and namespace and its policy grants the credential paths | | CLI TLS error | Local mTLS bundle does not match server cert/CA | Check `~/.config/openshell/gateways//mtls/` | diff --git a/tasks/test-odh.toml b/tasks/test-odh.toml index 8c67269641..c004776b66 100644 --- a/tasks/test-odh.toml +++ b/tasks/test-odh.toml @@ -12,11 +12,11 @@ ["e2e:odh"] description = "Run all ODH-specific tests (all tiers, ODH binary only), including image provenance" -run = "cargo test --manifest-path e2e/rust/Cargo.toml --features e2e-odh --test odh" +run = "e2e/rust/tests/odh/run-odh-test-tier.sh odh" ["e2e:odh:full"] description = "Run all tiers (upstream e2e + e2e-kubernetes + ODH), including image provenance" -run = "cargo test --manifest-path e2e/rust/Cargo.toml --features e2e-odh" +run = "e2e/rust/tests/odh/run-odh-test-tier.sh full" ["e2e:odh:smoke"] description = "Run Smoke tier: upstream + ODH (5 min target)" From cb193ef1c21a7b06332cb8fb7c1c49550bae13de Mon Sep 17 00:00:00 2001 From: Evan Lezar Date: Thu, 1 Oct 2026 15:05:35 +0000 Subject: [PATCH 24/33] ci: use package installers consistently in integration tests (#4056) * test(tmachine): add Fedora RPM package installer Signed-off-by: Evan Lezar * ci: qualify Ubuntu branch installs with DEB packages Signed-off-by: Evan Lezar * ci: align package installers across integration matrices Signed-off-by: Evan Lezar --------- Signed-off-by: Evan Lezar --- .github/workflows/branch-e2e.yml | 26 ++++++++++++++++--- .github/workflows/integration-runner.yml | 4 +-- .github/workflows/integration-test.yml | 8 +++--- .github/workflows/release-dev.yml | 4 +-- .github/workflows/release-tag.yml | 4 +-- CI.md | 11 +++++--- tests/ansible/playbooks/conformance/cli.yaml | 5 ++-- .../features/provider-refresh/keycloak.yaml | 5 ++-- 8 files changed, 47 insertions(+), 20 deletions(-) diff --git a/.github/workflows/branch-e2e.yml b/.github/workflows/branch-e2e.yml index 02b3508e76..eb903a55fc 100644 --- a/.github/workflows/branch-e2e.yml +++ b/.github/workflows/branch-e2e.yml @@ -89,6 +89,7 @@ jobs: timeout-minutes: 5 outputs: cargo: ${{ steps.version.outputs.cargo }} + deb_version: ${{ steps.version.outputs.deb_version }} rpm_version: ${{ steps.version.outputs.rpm_version }} rpm_release: ${{ steps.version.outputs.rpm_release }} steps: @@ -101,10 +102,12 @@ jobs: id: version run: | cargo="$(python3 tasks/scripts/release.py get-version --cargo)" + deb_version="$(python3 tasks/scripts/release.py get-version --dev --deb)" rpm_version="$(python3 tasks/scripts/release.py get-version --dev --rpm-version)" rpm_release="$(python3 tasks/scripts/release.py get-version --dev --rpm-release)" { echo "cargo=$cargo" + echo "deb_version=$deb_version" echo "rpm_version=$rpm_version" echo "rpm_release=$rpm_release" } >> "$GITHUB_OUTPUT" @@ -208,6 +211,18 @@ jobs: packages: write uses: ./.github/workflows/build-images.yml + build-deb: + name: Build Debian packages + needs: [pr_metadata, version, build-binaries, build-vm-driver] + if: needs.pr_metadata.outputs.run_integration == 'true' + permissions: + contents: read + packages: read + uses: ./.github/workflows/deb-package.yml + with: + checkout-ref: ${{ github.sha }} + deb-version: ${{ needs.version.outputs.deb_version }} + build-rpm: name: Build RPM packages needs: [pr_metadata, version, build-binaries] @@ -223,7 +238,7 @@ jobs: cargo-version: ${{ needs.version.outputs.cargo }} prepare-integration: - needs: [pr_metadata, build-binaries, build-images, build-rpm] + needs: [pr_metadata, build-binaries, build-images, build-deb, build-rpm] if: needs.pr_metadata.outputs.run_integration == 'true' permissions: actions: read @@ -231,6 +246,7 @@ jobs: packages: read uses: ./.github/workflows/prepare-integration-inputs.yml with: + deb-artifact-name: deb-linux-amd64 rpm-artifact-name: rpm-linux-x86_64 # Run driver-independent conformance tests. @@ -247,7 +263,7 @@ jobs: integration-inputs-artifact-id: ${{ needs.prepare-integration.outputs.integration_inputs_artifact_id }} test-matrix: >- [ - {"environment":"ubuntu-docker-rootful","installer":"binaries","testsuite":"conformance"}, + {"environment":"ubuntu-docker-rootful","installer":"deb","testsuite":"conformance"}, {"environment":"ubuntu-k3s","installer":"k3s","testsuite":"conformance"}, {"environment":"fedora-podman-rootful","installer":"rpm","testsuite":"conformance"}, {"environment":"fedora-podman-rootless","installer":"rpm","testsuite":"conformance"} @@ -267,11 +283,13 @@ jobs: integration-inputs-artifact-id: ${{ needs.prepare-integration.outputs.integration_inputs_artifact_id }} test-matrix: >- [ - {"environment":"fedora-podman-rootful","installer":"binaries","testsuite":"provider-refresh"}, - {"environment":"fedora-podman-rootless","installer":"binaries","testsuite":"provider-refresh"} + {"environment":"fedora-podman-rootful","installer":"rpm","testsuite":"provider-refresh"}, + {"environment":"fedora-podman-rootless","installer":"rpm","testsuite":"provider-refresh"} ] # Run driver-specific integration tests: + # These suites require the binary installer's CLI path, local HTTP gateway, + # and system service configuration for their Podman user-namespace fixtures. driver-specific-integration: needs: prepare-integration permissions: diff --git a/.github/workflows/integration-runner.yml b/.github/workflows/integration-runner.yml index 768879c7b8..089e73a242 100644 --- a/.github/workflows/integration-runner.yml +++ b/.github/workflows/integration-runner.yml @@ -26,8 +26,8 @@ on: [ {"environment":"ubuntu-docker-rootful","installer":"deb","testsuite":"conformance"}, {"environment":"ubuntu-k3s","installer":"k3s","testsuite":"conformance"}, - {"environment":"fedora-podman-rootful","installer":"binaries","testsuite":"conformance"}, - {"environment":"fedora-podman-rootless","installer":"binaries","testsuite":"conformance"} + {"environment":"fedora-podman-rootful","installer":"rpm","testsuite":"conformance"}, + {"environment":"fedora-podman-rootless","installer":"rpm","testsuite":"conformance"} ] permissions: diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 16141e6946..fe2428a0df 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -27,10 +27,10 @@ on: type: string default: >- [ - {"environment":"ubuntu-docker-rootful","installer":"binaries","testsuite":"conformance"}, + {"environment":"ubuntu-docker-rootful","installer":"deb","testsuite":"conformance"}, {"environment":"ubuntu-k3s","installer":"k3s","testsuite":"conformance"}, - {"environment":"fedora-podman-rootful","installer":"binaries","testsuite":"conformance"}, - {"environment":"fedora-podman-rootless","installer":"binaries","testsuite":"conformance"} + {"environment":"fedora-podman-rootful","installer":"rpm","testsuite":"conformance"}, + {"environment":"fedora-podman-rootless","installer":"rpm","testsuite":"conformance"} ] permissions: @@ -44,6 +44,8 @@ jobs: with: artifact-run-id: ${{ inputs['artifact-run-id'] }} source-sha: ${{ inputs['source-sha'] }} + deb-artifact-name: ${{ contains(inputs.test-matrix, '"deb"') && 'deb-linux-amd64' || '' }} + rpm-artifact-name: ${{ contains(inputs.test-matrix, '"rpm"') && 'rpm-linux-x86_64' || '' }} integration: needs: prepare diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml index 80db20c942..961f61ecde 100644 --- a/.github/workflows/release-dev.yml +++ b/.github/workflows/release-dev.yml @@ -144,8 +144,8 @@ jobs: integration-inputs-artifact-id: ${{ needs.prepare-integration.outputs.integration_inputs_artifact_id }} test-matrix: >- [ - {"environment":"fedora-podman-rootful","installer":"binaries","testsuite":"provider-refresh"}, - {"environment":"fedora-podman-rootless","installer":"binaries","testsuite":"provider-refresh"} + {"environment":"fedora-podman-rootful","installer":"rpm","testsuite":"provider-refresh"}, + {"environment":"fedora-podman-rootless","installer":"rpm","testsuite":"provider-refresh"} ] docker-e2e: diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index b57e45708b..da0ef42df2 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -195,8 +195,8 @@ jobs: integration-inputs-artifact-id: ${{ needs.prepare-integration.outputs.integration_inputs_artifact_id }} test-matrix: >- [ - {"environment":"fedora-podman-rootful","installer":"binaries","testsuite":"provider-refresh"}, - {"environment":"fedora-podman-rootless","installer":"binaries","testsuite":"provider-refresh"} + {"environment":"fedora-podman-rootful","installer":"rpm","testsuite":"provider-refresh"}, + {"environment":"fedora-podman-rootless","installer":"rpm","testsuite":"provider-refresh"} ] docker-e2e: diff --git a/CI.md b/CI.md index e5a6b7add1..c5a19e1f08 100644 --- a/CI.md +++ b/CI.md @@ -73,9 +73,14 @@ Main and manual runs also build release binaries, with `continue-on-error: true` so Windows failures do not fail the workflow. Every approved `Branch E2E Checks` run builds the RPM packages, including -runs without optional E2E labels. Core integration qualification installs the -CLI and gateway RPMs on Fedora with rootful and rootless Podman and runs conformance using -the matching runtime images. Release Dev and Release Tag run the same RPM lane. +runs without optional E2E labels. Core integration qualification builds and installs +the DEB on Ubuntu with Docker and installs the CLI and gateway RPMs on Fedora with +rootful and rootless Podman. These lanes run conformance using the matching runtime +images. Release Dev and Release Tag use the same package installers. +Fedora provider-refresh tests also use RPMs. The Podman driver-specific suites +retain the binary installer because their fixtures configure its system service, +local HTTP gateway, and CLI path. The manual Integration Tests workflow defaults +to the package installers and downloads the packages selected by its matrix. Three opt-in labels enable the long-running E2E suites: diff --git a/tests/ansible/playbooks/conformance/cli.yaml b/tests/ansible/playbooks/conformance/cli.yaml index fb3220b07e..dd8ce2b592 100644 --- a/tests/ansible/playbooks/conformance/cli.yaml +++ b/tests/ansible/playbooks/conformance/cli.yaml @@ -90,11 +90,12 @@ ansible.builtin.command: argv: - journalctl - - --unit - - openshell-gateway.service - --no-pager - --lines - "500" + - _SYSTEMD_UNIT=openshell-gateway.service + - "+" + - _SYSTEMD_USER_UNIT=openshell-gateway.service register: openshell_gateway_logs changed_when: false failed_when: false diff --git a/tests/ansible/playbooks/features/provider-refresh/keycloak.yaml b/tests/ansible/playbooks/features/provider-refresh/keycloak.yaml index 72ee9ba0fb..1aca72a488 100644 --- a/tests/ansible/playbooks/features/provider-refresh/keycloak.yaml +++ b/tests/ansible/playbooks/features/provider-refresh/keycloak.yaml @@ -88,11 +88,12 @@ ansible.builtin.command: argv: - journalctl - - --unit - - openshell-gateway.service - --no-pager - --lines - "500" + - _SYSTEMD_UNIT=openshell-gateway.service + - "+" + - _SYSTEMD_USER_UNIT=openshell-gateway.service register: openshell_gateway_logs changed_when: false failed_when: false From 1ad4e428a67c2a0869fd2043159acacea5d4683e Mon Sep 17 00:00:00 2001 From: Oliver Calder Date: Thu, 1 Oct 2026 15:10:38 +0000 Subject: [PATCH 25/33] fix(snap): simplify snap hooks (#3988) * fix(snap): simplify snap hooks The `post-refresh` hook runs after initial snap installation as well, so there is no need to call the `install` hook from within the `post-refresh` hook; instead, the logic can simply be moved into the `post-refresh` hook directly, and the `install` hook removed. Also, the existing `install` hook logic looked for an insecure configuration, and if found, replaced the entire configuration file with a minimal default in the current format. But OpenShell does that default behavior without any config file, so we may as well simply remove the configuration file entirely to keep up-to-date with the current default behavior. Let OpenShell create a configuration file if it needs to, rather than auto-create one via the packaging scripts. Signed-off-by: Oliver Calder * fix(snap): remove the connect-plug-docker hook The `openshell:docker` is auto-connected to the system `:docker` slot, so there should not be a need to separately restart the gateway service when the interface is connected. For locally-built test snaps which were not published to the store, the autoconnection is not made, but when the snap is installed, the gateway will attempt to start anyway and fail to find any available compute driver, so quickly restart until it hits the systemd start-limit, after which systemd prevents the service from being started again. If a user tries to manually connect their locally-built `openshell` snap to the `:docker` slot, then the `connect-plug-docker` hook runs and triggers a restart of the gateway, which will usually fail because the start limit has already been hit. An error in the hook will thus cause the interface connection to be undone, which is undesirable. Thus, we can remove this hook entirely, and instead allow interface connections to succeed as intended. The user still needs to manually restart the gateway service after making a manual connection (as was the case previously) and probably needs to `systemctl reset-failed` first, but at least connection will succeed beforehand so they can proceed with these steps. Signed-off-by: Oliver Calder * fix(snap): set refresh-mode: endure again, with manual restart Return to the previous behavior before commit a67567e58, where the gateway is not stopped before refreshes. The `post-refresh` hook now restarts the gateway if the TLS configuration was corrected, so we don't have to enforce restarting the gateway on every refresh even when not necessary. Thus, set `refresh-mode: endure`, and let the hook decide when the gateway needs to be restarted. Signed-off-by: Oliver Calder * fix(snap): update docs and tests to reflect snap hook changes Signed-off-by: Oliver Calder * docs(snap): remove verbose explanation of snap gateway refresh behavior Signed-off-by: Oliver Calder --------- Signed-off-by: Oliver Calder --- docs/about/installation.mdx | 4 +- python/openshell/release_formula_test.py | 12 -- snap/hooks/connect-plug-docker | 13 -- snap/hooks/install | 35 ----- snap/hooks/post-refresh | 24 ++- snapcraft.yaml | 7 +- tasks/scripts/test-packaging-assets.sh | 29 ++-- tasks/scripts/test-snap-install-hook.sh | 142 ------------------ tasks/scripts/test-snap-post-refresh-hook.sh | 147 +++++++++++++++++++ 9 files changed, 191 insertions(+), 222 deletions(-) delete mode 100755 snap/hooks/connect-plug-docker delete mode 100755 snap/hooks/install delete mode 100755 tasks/scripts/test-snap-install-hook.sh create mode 100755 tasks/scripts/test-snap-post-refresh-hook.sh diff --git a/docs/about/installation.mdx b/docs/about/installation.mdx index d41639cddc..a279bb3510 100644 --- a/docs/about/installation.mdx +++ b/docs/about/installation.mdx @@ -126,13 +126,15 @@ openshell status Keep the client key private. -To install a locally built snap, connect its interfaces manually: +To install a locally built snap, connect its interfaces manually. The gateway may reach systemd's start limit before Docker is connected, so reset the failed unit and restart the gateway after connecting the interfaces: ```shell sudo snap install ./openshell_*.snap --dangerous sudo snap connect openshell:log-observe sudo snap connect openshell:system-observe sudo snap connect openshell:docker :docker +sudo systemctl reset-failed snap.openshell.gateway.service +sudo snap restart openshell.gateway ``` ## Kubernetes diff --git a/python/openshell/release_formula_test.py b/python/openshell/release_formula_test.py index e1bc1c9c54..b845c83090 100644 --- a/python/openshell/release_formula_test.py +++ b/python/openshell/release_formula_test.py @@ -4,7 +4,6 @@ from __future__ import annotations import re -import stat import subprocess import sys from pathlib import Path @@ -168,17 +167,6 @@ def test_snap_wrapper_uses_optional_gateway_config_without_generating_toml() -> assert 'exec "${SNAP}/bin/openshell-gateway" "$@"' in wrapper -def test_snap_docker_connect_hook_restarts_gateway() -> None: - repo_root = Path(__file__).resolve().parents[2] - hook = repo_root / "snap/hooks/connect-plug-docker" - - assert hook.is_file() - assert hook.stat().st_mode & stat.S_IXUSR - assert 'snapctl restart "${SNAP_INSTANCE_NAME}.gateway"' in hook.read_text( - encoding="utf-8" - ) - - def test_rpm_spec_seeds_and_migrates_gateway_defaults() -> None: repo_root = Path(__file__).resolve().parents[2] spec = (repo_root / "openshell.spec").read_text(encoding="utf-8") diff --git a/snap/hooks/connect-plug-docker b/snap/hooks/connect-plug-docker deleted file mode 100755 index 6e0e00caa4..0000000000 --- a/snap/hooks/connect-plug-docker +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# The gateway daemon can start when this plug is still disconnected. Restart it -# after Docker access becomes available so driver auto-detection runs with the -# socket exposed through the system :docker slot. This hook does not make normal -# gateway startup conditional on Docker; it runs after an automatic or manual -# Docker connection. - -set -eu - -snapctl restart "${SNAP_INSTANCE_NAME}.gateway" diff --git a/snap/hooks/install b/snap/hooks/install deleted file mode 100755 index 78efeefdf8..0000000000 --- a/snap/hooks/install +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Create the mTLS default and replace insecure configs on refresh. - -set -eu - -config_file="${SNAP_COMMON}/gateway.toml" -insecure='^[[:space:]]*(allow_unauthenticated_users|disable_tls)[[:space:]]*=[[:space:]]*true([[:space:]#]|$)' - -# Keep secure operator configs, symlinks, and directories. Replace a config -# that explicitly allows plaintext or anonymous access, even if it has other -# edits. -if [ -L "$config_file" ]; then - exit 0 -elif [ -f "$config_file" ]; then - grep -Eq "$insecure" "$config_file" || exit 0 - echo "openshell: replacing insecure gateway config with the mTLS default" >&2 -elif [ -e "$config_file" ]; then - exit 0 -fi - -mkdir -p "$SNAP_COMMON" -umask 077 -temporary_file=$(mktemp "${config_file}.tmp.XXXXXX") -trap 'rm -f "$temporary_file"' 0 HUP INT TERM -cat >"$temporary_file" <<'CONFIG' -[openshell] -version = 2 - -[openshell.gateway] -CONFIG -mv -f "$temporary_file" "$config_file" -trap - 0 HUP INT TERM diff --git a/snap/hooks/post-refresh b/snap/hooks/post-refresh index 28eb20373d..14fbe5d482 100755 --- a/snap/hooks/post-refresh +++ b/snap/hooks/post-refresh @@ -2,11 +2,29 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -# Replace insecure gateway configs on refresh, then restart the gateway so the -# new config takes effect even when the previous revision used +# Remove insecure gateway configs on refresh, then restart the gateway so the +# default config takes effect even when the previous revision used # refresh-mode: endure and kept its plaintext gateway running. set -eu -"${SNAP}/meta/hooks/install" +config_file="${SNAP_COMMON}/gateway.toml" +insecure='^[[:space:]]*(allow_unauthenticated_users|disable_tls)[[:space:]]*=[[:space:]]*true([[:space:]#]|$)' + +# Keep secure operator configs, symlinks, and directories. Remove a config +# that explicitly allows plaintext or anonymous access, even if it has other +# edits. +if [ ! -e "$config_file" ]; then + exit 0 +elif [ -L "$config_file" ]; then + exit 0 +elif [ -f "$config_file" ]; then + grep -Eq "$insecure" "$config_file" || exit 0 + echo "openshell: removing insecure gateway config to use the mTLS default" >&2 +elif [ -e "$config_file" ]; then + exit 0 +fi + +rm -f "$config_file" + snapctl restart "${SNAP_INSTANCE_NAME}.gateway" diff --git a/snapcraft.yaml b/snapcraft.yaml index 6c943c1387..c05011e1d9 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -88,9 +88,10 @@ apps: gateway: command: bin/openshell-gateway-wrapper daemon: simple - # Refresh must activate the migrated mTLS config immediately. This - # interrupts active sandbox sessions. - refresh-mode: restart + # refresh-mode: endure prevents snapd from restarting the gateway daemon + # during snap refreshes, which would kill active sandbox sessions. + # Operators must manually restart the service after a refresh if needed. + refresh-mode: endure # Snapd runs this daemon as root. The wrapper serves TLS from the bundle # generated in $SNAP_COMMON/tls, and the default config requires client # certificates; the installer copies the client bundle to the target diff --git a/tasks/scripts/test-packaging-assets.sh b/tasks/scripts/test-packaging-assets.sh index 04f69b08be..b9ade5eb05 100755 --- a/tasks/scripts/test-packaging-assets.sh +++ b/tasks/scripts/test-packaging-assets.sh @@ -83,16 +83,14 @@ snapcraft="${ROOT}/snapcraft.yaml" snap_install_docs="${ROOT}/docs/about/installation.mdx" snap_canary="${ROOT}/.github/workflows/release-canary.yml" snap_repro="${ROOT}/nix/test-guest/scripts/snap-gateway-repro.sh" -snap_docker_hook="${ROOT}/snap/hooks/connect-plug-docker" -snap_install_hook="${ROOT}/snap/hooks/install" +snap_post_refresh_hook="${ROOT}/snap/hooks/post-refresh" package_deb="${ROOT}/tasks/scripts/package-deb.sh" assert_file_exists "$snap_wrapper" assert_file_exists "$snapcraft" assert_file_exists "$snap_install_docs" assert_file_exists "$snap_canary" assert_file_exists "$snap_repro" -assert_file_exists "$snap_docker_hook" -assert_file_exists "$snap_install_hook" +assert_file_exists "$snap_post_refresh_hook" assert_file_exists "$package_deb" assert_contains "$service" "ExecStartPre=/usr/bin/openshell-gateway config preflight" assert_contains "$package_deb" "\$src_dir/openshell-gateway.service" @@ -114,28 +112,33 @@ for snap_file in \ "$snap_install_docs" \ "$snap_canary" \ "$snap_repro" \ - "$snap_docker_hook" \ - "$snap_install_hook"; do + "$snap_post_refresh_hook"; do assert_not_contains "$snap_file" "docker:docker-daemon" assert_not_contains "$snap_file" "default-provider: docker" done -if [[ ! -x "$snap_install_hook" ]]; then - echo "FAIL: Snap install hook must be executable" >&2 +if [[ -e "${ROOT}/snap/hooks/connect-plug-docker" ]]; then + echo "FAIL: obsolete Snap Docker connection hook must not exist" >&2 exit 1 fi -assert_not_contains "$snap_install_hook" 'compute_driver' -assert_not_contains "$snap_install_hook" 'allow_unauthenticated_users = true' -assert_contains "$snapcraft" 'refresh-mode: restart' -if [[ ! -x "$(dirname "$snap_install_hook")/post-refresh" ]]; then +if [[ -e "${ROOT}/snap/hooks/install" ]]; then + echo "FAIL: obsolete Snap install hook must not exist" >&2 + exit 1 +fi +assert_contains "$snapcraft" 'refresh-mode: endure' +if [[ ! -x "$snap_post_refresh_hook" ]]; then echo "FAIL: Snap post-refresh hook must be executable" >&2 exit 1 fi assert_not_contains "$ROOT/tasks/scripts/snap-gateway-wrapper.sh" 'OPENSHELL_DISABLE_TLS' -bash "$ROOT/tasks/scripts/test-snap-install-hook.sh" "$snap_install_hook" +bash "$ROOT/tasks/scripts/test-snap-post-refresh-hook.sh" "$snap_post_refresh_hook" assert_not_contains "$snap_install_docs" "snap connect openshell:home" assert_not_contains "$snap_install_docs" "snap connect openshell:network" assert_not_contains "$snap_install_docs" "snap connect openshell:network-bind" assert_contains "$snap_install_docs" "snap connect openshell:docker :docker" +assert_contains "$snap_install_docs" "systemctl reset-failed snap.openshell.gateway.service" +assert_contains "$snap_install_docs" "snap restart openshell.gateway" +assert_contains "$snap_install_docs" "Snap refreshes keep the running gateway process active" +assert_contains "$snap_install_docs" "install script refreshes and restarts the gateway automatically" assert_contains "$snap_canary" "install.sh | sh" assert_contains "$snap_canary" "ubuntu-snap-system-docker:" assert_contains "$snap_canary" "ubuntu-snap-docker-preflight:" diff --git a/tasks/scripts/test-snap-install-hook.sh b/tasks/scripts/test-snap-install-hook.sh deleted file mode 100755 index e82cb64b55..0000000000 --- a/tasks/scripts/test-snap-install-hook.sh +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env bash -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -set -euo pipefail - -hook_input=${1:?Usage: test-snap-install-hook.sh } -hook_dir=$(cd "$(dirname "$hook_input")" && pwd) -hook="${hook_dir}/$(basename "$hook_input")" -work=$(mktemp -d "${TMPDIR:-/tmp}/openshell snap install hook.XXXXXX") -trap 'rm -rf "$work"' EXIT - -expected="${work}/expected.toml" -cat >"$expected" <<'EOF' -[openshell] -version = 2 - -[openshell.gateway] -EOF - -legacy="${work}/legacy.toml" -cat >"$legacy" <<'EOF' -[openshell] -version = 2 - -[openshell.gateway] - -[openshell.gateway.auth] -allow_unauthenticated_users = true -EOF - -common="${work}/fresh" -SNAP_COMMON="$common" "$hook" -cmp -s "$expected" "$common/gateway.toml" -if [[ -z $(find "$common/gateway.toml" -perm 600) ]]; then - echo "FAIL: install hook config must be mode 0600" >&2 - exit 1 -fi - -printf '\noperator setting = true\n' >>"$common/gateway.toml" -cp "$common/gateway.toml" "${work}/operator-before" -SNAP_COMMON="$common" "$hook" -cmp -s "${work}/operator-before" "$common/gateway.toml" - -common="${work}/legacy" -mkdir -p "$common" -cp "$legacy" "$common/gateway.toml" -chmod 644 "$common/gateway.toml" -SNAP_COMMON="$common" "$hook" -if ! cmp -s "$expected" "$common/gateway.toml"; then - echo "FAIL: install hook must migrate the legacy unauthenticated config" >&2 - exit 1 -fi -if [[ -z $(find "$common/gateway.toml" -perm 600) ]]; then - echo "FAIL: migrated config must be mode 0600" >&2 - exit 1 -fi - -common="${work}/legacy-edited" -mkdir -p "$common" -cp "$legacy" "$common/gateway.toml" -printf '\n# operator note\n' >>"$common/gateway.toml" -cp "$common/gateway.toml" "${work}/legacy-edited-before" -SNAP_COMMON="$common" "$hook" -cmp -s "$expected" "$common/gateway.toml" - -common="${work}/custom-insecure" -mkdir -p "$common" -cat >"$common/gateway.toml" <<'EOF' -[openshell] -version = 2 - -[openshell.gateway] -compute_driver = "docker" -disable_tls = true # old local override - -[openshell.gateway.auth] -allow_unauthenticated_users = true # old local override -EOF -cp "$common/gateway.toml" "${work}/custom-insecure-before" -SNAP_COMMON="$common" "$hook" -cmp -s "$expected" "$common/gateway.toml" - -common="${work}/custom-secure" -mkdir -p "$common" -cat >"$common/gateway.toml" <<'EOF' -[openshell] -version = 2 - -[openshell.gateway] -compute_driver = "docker" -# allow_unauthenticated_users = true -EOF -cp "$common/gateway.toml" "${work}/custom-secure-before" -SNAP_COMMON="$common" "$hook" -cmp -s "${work}/custom-secure-before" "$common/gateway.toml" - -common="${work}/post-refresh" -mkdir -p "$common" "${work}/snap/meta/hooks" -cp "$hook" "${work}/snap/meta/hooks/install" -cp "${work}/legacy-edited-before" "$common/gateway.toml" -mkdir -p "${work}/bin" -cat >"${work}/bin/snapctl" <>"${work}/snapctl.log" -EOF -chmod 755 "${work}/bin/snapctl" -PATH="${work}/bin:$PATH" SNAP="${work}/snap" SNAP_COMMON="$common" \ - SNAP_INSTANCE_NAME=openshell "${hook_dir}/post-refresh" -if ! cmp -s "$expected" "$common/gateway.toml"; then - echo "FAIL: post-refresh hook must migrate an edited insecure config" >&2 - exit 1 -fi -if [[ $(cat "${work}/snapctl.log") != "restart openshell.gateway" ]]; then - echo "FAIL: post-refresh hook must restart the gateway" >&2 - cat "${work}/snapctl.log" >&2 - exit 1 -fi - -common="${work}/broken-link" -mkdir -p "$common" -ln -s "${work}/missing-target" "$common/gateway.toml" -SNAP_COMMON="$common" "$hook" -if [[ $(readlink "$common/gateway.toml") != "${work}/missing-target" ]]; then - echo "FAIL: install hook replaced a broken operator symlink" >&2 - exit 1 -fi - -common="${work}/directory" -mkdir -p "$common/gateway.toml" -SNAP_COMMON="$common" "$hook" -if [[ ! -d "$common/gateway.toml" ]]; then - echo "FAIL: install hook replaced an operator-owned directory" >&2 - exit 1 -fi - -if [[ -n $(find "$work" -name 'gateway.toml.pre-mtls*') ]]; then - echo "FAIL: install hook must not keep copies of replaced configs" >&2 - exit 1 -fi - -echo "Snap install hook tests passed" diff --git a/tasks/scripts/test-snap-post-refresh-hook.sh b/tasks/scripts/test-snap-post-refresh-hook.sh new file mode 100755 index 0000000000..1f97ac2598 --- /dev/null +++ b/tasks/scripts/test-snap-post-refresh-hook.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +hook_input=${1:?Usage: test-snap-post-refresh-hook.sh } +hook_dir=$(cd "$(dirname "$hook_input")" && pwd) +hook="${hook_dir}/$(basename "$hook_input")" +work=$(mktemp -d "${TMPDIR:-/tmp}/openshell snap post-refresh hook.XXXXXX") +trap 'rm -rf "$work"' EXIT + +mkdir -p "${work}/bin" +cat >"${work}/bin/snapctl" <>"${work}/snapctl.log" +EOF +chmod 755 "${work}/bin/snapctl" + +run_hook() { + local common=$1 + + PATH="${work}/bin:$PATH" SNAP_COMMON="$common" SNAP_INSTANCE_NAME=openshell "$hook" +} + +assert_no_restart() { + local name=$1 + local common=$2 + + rm -f "${work}/snapctl.log" + run_hook "$common" + if [[ -e "${work}/snapctl.log" ]]; then + echo "FAIL: post-refresh hook restarted the gateway for ${name}" >&2 + cat "${work}/snapctl.log" >&2 + exit 1 + fi +} + +assert_removed_and_restarted() { + local name=$1 + local common=$2 + + rm -f "${work}/snapctl.log" + run_hook "$common" + if [[ -e "$common/gateway.toml" ]] || [[ -L "$common/gateway.toml" ]]; then + echo "FAIL: post-refresh hook did not remove ${name}" >&2 + exit 1 + fi + if [[ $(cat "${work}/snapctl.log") != "restart openshell.gateway" ]]; then + echo "FAIL: post-refresh hook did not restart the gateway once for ${name}" >&2 + cat "${work}/snapctl.log" >&2 + exit 1 + fi +} + +common="${work}/missing" +mkdir -p "$common" +assert_no_restart "a missing config" "$common" +if [[ -e "$common/gateway.toml" ]] || [[ -L "$common/gateway.toml" ]]; then + echo "FAIL: post-refresh hook created a missing config" >&2 + exit 1 +fi + +common="${work}/secure" +mkdir -p "$common" +cat >"$common/gateway.toml" <<'EOF' +[openshell] +version = 2 + +[openshell.gateway] +compute_driver = "docker" +disable_tls = false + +[openshell.gateway.auth] +allow_unauthenticated_users = false +# allow_unauthenticated_users = true +EOF +cp "$common/gateway.toml" "${work}/secure-before" +assert_no_restart "a secure config" "$common" +cmp -s "${work}/secure-before" "$common/gateway.toml" + +common="${work}/unauthenticated" +mkdir -p "$common" +cat >"$common/gateway.toml" <<'EOF' +[openshell.gateway.auth] +allow_unauthenticated_users = true +EOF +assert_removed_and_restarted "an unauthenticated config" "$common" + +common="${work}/tls-disabled" +mkdir -p "$common" +cat >"$common/gateway.toml" <<'EOF' +[openshell.gateway] +disable_tls = true # old local override + +# operator note +EOF +assert_removed_and_restarted "an edited TLS-disabled config" "$common" + +common="${work}/broken-link" +mkdir -p "$common" +ln -s "${work}/missing-target" "$common/gateway.toml" +assert_no_restart "a broken operator symlink" "$common" +if [[ $(readlink "$common/gateway.toml") != "${work}/missing-target" ]]; then + echo "FAIL: post-refresh hook replaced a broken operator symlink" >&2 + exit 1 +fi + +common="${work}/existing-link" +mkdir -p "$common" +printf '%s\n' 'disable_tls = true' >"${work}/linked-config.toml" +ln -s "${work}/linked-config.toml" "$common/gateway.toml" +assert_no_restart "an existing operator symlink" "$common" +if [[ $(readlink "$common/gateway.toml") != "${work}/linked-config.toml" ]]; then + echo "FAIL: post-refresh hook replaced an existing operator symlink" >&2 + exit 1 +fi + +common="${work}/directory" +mkdir -p "$common/gateway.toml" +assert_no_restart "an operator-owned directory" "$common" +if [[ ! -d "$common/gateway.toml" ]]; then + echo "FAIL: post-refresh hook replaced an operator-owned directory" >&2 + exit 1 +fi + +common="${work}/remove-failure" +mkdir -p "$common" "${work}/failing-bin" +printf '%s\n' 'disable_tls = true' >"$common/gateway.toml" +cat >"${work}/failing-bin/rm" <<'EOF' +#!/bin/sh +exit 1 +EOF +chmod 755 "${work}/failing-bin/rm" +rm -f "${work}/snapctl.log" +if PATH="${work}/failing-bin:${work}/bin:$PATH" SNAP_COMMON="$common" \ + SNAP_INSTANCE_NAME=openshell "$hook"; then + echo "FAIL: post-refresh hook succeeded when config removal failed" >&2 + exit 1 +fi +if [[ -e "${work}/snapctl.log" ]]; then + echo "FAIL: post-refresh hook restarted after config removal failed" >&2 + cat "${work}/snapctl.log" >&2 + exit 1 +fi + +echo "Snap post-refresh hook tests passed" From f2901393e6a47bf51a8e5e804edab777f012bb39 Mon Sep 17 00:00:00 2001 From: Shiju Date: Thu, 1 Oct 2026 16:11:05 +0000 Subject: [PATCH 26/33] fix(supervisor): wait for repair when the gateway refuses a startup policy write (#3785) * fix(supervisor): wait for repair when the gateway refuses a startup policy write Startup writes the sandbox policy to the gateway in two cases: it uploads a discovered image policy when the gateway has none, and it writes the policy back after adding the proxy baseline filesystem paths. When the gateway refused either write with FAILED_PRECONDITION or INVALID_ARGUMENT, for example because the policy binds a provider that is not attached, startup treated the refusal as a permanent error and the supervisor exited. The sandbox never reached the ConfigurationInvalid repair state that other startup rejections use. Report such a refusal as a configuration rejection carrying the gateway's message, log it once per write and error code, and keep polling, so attaching the provider or replacing the policy completes startup. Other error codes keep their current handling: transient codes are retried, and permission, not-found and authentication failures still end startup. Skip the baseline-path write-back while a global policy is active. The gateway refuses every sandbox policy write in that state, so startup exited whenever a global policy lacked a baseline path. The supervisor now adds the paths to its own copy of the policy without saving a revision. Signed-off-by: Shiju * test(supervisor): stabilize startup refusal log capture Keep a second tracing dispatcher alive while capturing startup refusal logs. With only one dispatcher, a parallel test thread without a default subscriber can cache Interest::never for the shared OCSF callsite after the capture thread rebuilds the cache. Preserve the exact log-count, diagnostic, configuration-generation and repair assertions. Production startup behavior is unchanged. Signed-off-by: Shiju * fix(supervisor): reconcile stale startup rejection reports Refetch desired configuration immediately when a rejection report is aborted because its generation changed. Preserve acknowledged rejection pacing and all other report error handling. Signed-off-by: Shiju * test(supervisor): box startup repair race futures Keep the repair regressions below the large-future lint threshold without changing their inputs, scheduling, or assertions. Signed-off-by: Shiju --------- Signed-off-by: Shiju --- crates/openshell-supervisor/src/lib.rs | 1082 ++++++++++++++++- docs/how-it-works/policies/default-policy.mdx | 4 +- .../how-it-works/policies/manage-policies.mdx | 2 + 3 files changed, 1068 insertions(+), 20 deletions(-) diff --git a/crates/openshell-supervisor/src/lib.rs b/crates/openshell-supervisor/src/lib.rs index 63c5302673..99607dbedb 100644 --- a/crates/openshell-supervisor/src/lib.rs +++ b/crates/openshell-supervisor/src/lib.rs @@ -2244,6 +2244,7 @@ async fn load_policy_with_gateway( &instance_id, &snapshot, &snapshot.configuration_error, + None, ) .await?; reconciliation_attempts = 0; @@ -2268,7 +2269,7 @@ async fn load_policy_with_gateway( ImagePolicyDiscovery::Policy(policy) => *policy.clone(), ImagePolicyDiscovery::Missing => openshell_policy::restrictive_default_policy(), ImagePolicyDiscovery::Invalid => { - reject_startup_configuration(gateway, &mut rejection_log, id, &instance_id, &snapshot, "Image policy is invalid; replace the sandbox policy to repair configuration").await?; + reject_startup_configuration(gateway, &mut rejection_log, id, &instance_id, &snapshot, "Image policy is invalid; replace the sandbox policy to repair configuration", None).await?; reconciliation_attempts = 0; continue; } @@ -2280,10 +2281,33 @@ async fn load_policy_with_gateway( // Sync and re-fetch over a single connection to avoid extra // TLS handshakes. let ws = snapshot.workspace.clone(); - snapshot = grpc_retry("Image policy synchronization", || { + let synced = grpc_retry("Image policy synchronization", || { gateway.sync(sandbox, &discovered, &ws) }) - .await?; + .await; + snapshot = match synced { + Ok(synced) => synced, + Err(error) => { + // The gateway stored nothing, so report the rejection + // against the snapshot this upload was built from and + // upload again on the next pass. Attaching the missing + // provider or setting a sandbox policy repairs startup. + let rejection = + startup_write_rejection("image policy", &error).ok_or(error)?; + reject_startup_configuration( + gateway, + &mut rejection_log, + id, + &instance_id, + &snapshot, + &rejection.diagnostic, + Some(&rejection.log_key), + ) + .await?; + reconciliation_attempts = 0; + continue; + } + }; if let Some(policy) = snapshot.policy.clone() { policy } else { @@ -2299,6 +2323,7 @@ async fn load_policy_with_gateway( &instance_id, &snapshot, "Effective policy is unavailable after image discovery", + None, ) .await?; reconciliation_attempts = 0; @@ -2308,14 +2333,44 @@ async fn load_policy_with_gateway( // Ensure baseline filesystem paths are present for proxy-mode // sandboxes. If the policy was enriched, sync the updated version - // back to the gateway so users can see the effective policy. + // back to the gateway so users can see the effective policy. Only + // a sandbox-sourced policy is written back. The gateway refuses + // every sandbox policy write while a global policy is active, so a + // global policy keeps the added paths in this process only. let enriched = enrich_proto_baseline_paths(&mut proto_policy); - let sync_policy = proto_sync_payload_for_enriched_policy(&proto_policy, enriched); + let sync_policy = proto_sync_payload_for_enriched_policy(&proto_policy, enriched) + .filter(|_| snapshot.policy_source == openshell_core::proto::PolicySource::Sandbox); if let Some(sync_policy) = sync_policy { - let canonical = grpc_retry("Enriched policy synchronization", || { + let synced = grpc_retry("Enriched policy synchronization", || { gateway.sync(sandbox, &sync_policy, &snapshot.workspace) }) - .await?; + .await; + let canonical = match synced { + Ok(canonical) => canonical, + Err(error) => { + // The stored policy is unchanged, so report the + // rejection against the snapshot it was read from and + // read again on the next pass, which picks up a + // replacement policy or a provider repair. + let rejection = startup_write_rejection( + "policy update that adds baseline filesystem paths", + &error, + ) + .ok_or(error)?; + reject_startup_configuration( + gateway, + &mut rejection_log, + id, + &instance_id, + &snapshot, + &rejection.diagnostic, + Some(&rejection.log_key), + ) + .await?; + reconciliation_attempts = 0; + continue; + } + }; proto_policy = canonical.policy.clone().ok_or_else(|| { miette::miette!("Gateway returned no effective policy after enrichment") })?; @@ -2347,6 +2402,7 @@ async fn load_policy_with_gateway( } else { &snapshot.configuration_error }, + None, ) .await?; reconciliation_attempts = 0; @@ -2378,6 +2434,7 @@ async fn load_policy_with_gateway( &instance_id, &snapshot, "Policy or provider environment failed runtime validation", + None, ) .await?; reconciliation_attempts = 0; @@ -2595,6 +2652,64 @@ fn prepare_provider_environment( Ok(prepared) } +/// Returns the rejection for a startup policy write that the gateway refused +/// for a reason someone can repair while the sandbox waits, or `None` when +/// startup must keep treating the error as fatal. +/// +/// `FAILED_PRECONDITION` (for example a `credential_binding` that names a +/// provider the sandbox does not have) and `INVALID_ARGUMENT` (a policy that +/// fails validation) are repaired by attaching a provider or replacing the +/// sandbox policy, so the caller reports them as a rejected configuration and +/// polls again. Every other error keeps its existing handling: `grpc_retry` has +/// already retried transient codes, and its error after the last attempt +/// carries no status. `PERMISSION_DENIED`, `NOT_FOUND` and `UNAUTHENTICATED` +/// mean the supervisor's identity or the sandbox record is wrong, which no +/// policy or provider change repairs. +/// +/// The diagnostic carries the gateway's message so the sandbox log names the +/// cause. Like the gateway's own configuration diagnostics, it drops control +/// characters and keeps at most 512 characters of that message. +fn startup_write_rejection(write: &str, error: &miette::Report) -> Option { + let mut source: Option<&dyn std::error::Error> = Some(error.as_ref()); + while let Some(cause) = source { + if let Some(status) = cause.downcast_ref::() { + if !matches!( + status.code(), + tonic::Code::FailedPrecondition | tonic::Code::InvalidArgument + ) { + return None; + } + let message: String = status + .message() + .chars() + .filter(|character| !character.is_control()) + .take(512) + .collect(); + return Some(StartupWriteRejection { + diagnostic: format!("Gateway rejected the {write}: {message}"), + log_key: format!("Gateway rejected the {write} ({:?})", status.code()), + }); + } + source = cause.source(); + } + None +} + +/// A startup policy write that the gateway refused for a repairable reason. +#[derive(Debug, PartialEq, Eq)] +struct StartupWriteRejection { + /// The write that failed and the gateway's bounded message. Startup + /// reports it on every pass and logs it when `log_key` or the snapshot + /// changes. + diagnostic: String, + /// Decides whether a repeated refusal is logged again. It names the write + /// and the gRPC code but not the gateway's message, because the gateway + /// walks unordered maps when it validates a policy and can name a + /// different problem of the same policy on each attempt. A refusal of the + /// same write against the same snapshot is therefore logged once. + log_key: String, +} + // Retain only the most recent rejection, so A -> B -> A emits all transitions. #[derive(Default)] struct StartupRejectionLog(Option<(LoadedPolicyRevision, String)>); @@ -2603,11 +2718,11 @@ impl StartupRejectionLog { fn changed( &mut self, snapshot: &openshell_core::grpc_client::SettingsPollResult, - error: &str, + key: &str, ) -> bool { let rejection = ( LoadedPolicyRevision::from_snapshot(snapshot), - error.to_owned(), + key.to_owned(), ); if self.0.as_ref() == Some(&rejection) { return false; @@ -2624,20 +2739,46 @@ async fn reject_startup_configuration( instance_id: &str, snapshot: &openshell_core::grpc_client::SettingsPollResult, error: &str, + log_key: Option<&str>, ) -> Result<()> { - grpc_retry("Startup rejection report", || { - gateway.report( - sandbox_id, - instance_id, - Some(snapshot), - openshell_core::proto::ConfigurationAdmissionState::Rejected, - error, - ) + let recorded = grpc_retry("Startup rejection report", || async { + match gateway + .report( + sandbox_id, + instance_id, + Some(snapshot), + openshell_core::proto::ConfigurationAdmissionState::Rejected, + error, + ) + .await + { + Ok(()) => Ok(true), + Err(error) + if error.chain().any(|cause| { + cause + .downcast_ref::() + .is_some_and(|status| status.code() == tonic::Code::Aborted) + }) => + { + // The desired generation changed while this report was in + // flight. Replaying the old snapshot cannot succeed; let the + // caller fetch the repair immediately without logging a stale + // rejection or delaying it as an unchanged configuration. + Ok(false) + } + Err(error) => Err(error), + } }) .await?; + if !recorded { + return Ok(()); + } // Keep reporting readiness on every retry, but log only changed rejections. - // Fixed, bounded diagnostics deliberately omit the candidate and credentials. - if rejection_log.changed(snapshot, error) { + // The log key is the diagnostic itself unless the caller passes a stable + // `log_key` because its diagnostic text can vary between identical + // rejections. Diagnostics are fixed strings or bounded gateway messages; + // callers never pass the candidate policy or credential values. + if rejection_log.changed(snapshot, log_key.unwrap_or(error)) { ocsf_emit!( ConfigStateChangeBuilder::new(ocsf_ctx()) .severity(SeverityId::High) @@ -5565,6 +5706,909 @@ network_policies: ); } + // ---- Startup policy write refusal tests ---- + + /// `UpdateConfig` diagnostic for a credential binding whose provider is not + /// attached to the sandbox. The gateway returns it as `FAILED_PRECONDITION`. + const UNATTACHED_PROVIDER_DIAGNOSTIC: &str = "credential_binding references provider 'github', but that provider is not attached to the sandbox"; + + /// The same refusal naming a second unattached provider of one policy. The + /// gateway reports whichever unattached provider it meets first, so one + /// policy can be refused with either message. + const UNATTACHED_SECOND_PROVIDER_DIAGNOSTIC: &str = "credential_binding references provider 'gitlab', but that provider is not attached to the sandbox"; + + /// `UpdateConfig` diagnostic for middleware configuration the gateway + /// cannot validate. The gateway returns it as `INVALID_ARGUMENT`. + const INVALID_MIDDLEWARE_DIAGNOSTIC: &str = + "policy middleware validation failed: binding 'redact' has an invalid config"; + + /// `UpdateConfig` diagnostic for any sandbox policy write while a global + /// policy is active. The gateway returns it as `FAILED_PRECONDITION`. + const GLOBAL_POLICY_DIAGNOSTIC: &str = + "policy is managed globally; delete global policy before sandbox policy update"; + + /// One gateway call made by startup, recorded in call order. + #[derive(Clone, Debug, PartialEq, Eq)] + enum StartupCall { + Snapshot, + Sync, + Report(StartupReport), + } + + /// One admission report startup sent to the gateway. + #[derive(Clone, Debug, PartialEq, Eq)] + struct StartupReport { + state: openshell_core::proto::ConfigurationAdmissionState, + error: String, + /// Generation of the snapshot sent with the report. The gateway + /// answers `ABORTED` to a `Rejected` or `Accepted` report whose + /// generation is no longer current. + generation: Option, + } + + /// `(version, policy_hash, config_revision, provider_env_revision)` of a + /// snapshot, the fields the gateway compares when it admits a report. + type ReportGeneration = (u32, String, u64, u64); + + fn report_generation( + snapshot: &openshell_core::grpc_client::SettingsPollResult, + ) -> ReportGeneration { + ( + snapshot.version, + snapshot.policy_hash.clone(), + snapshot.config_revision, + snapshot.provider_env_revision, + ) + } + + /// Snapshot of a sandbox with no stored policy, as the gateway returns it: + /// version 0 and an empty policy hash. The configuration and provider + /// revisions are nonzero, so a report built from any other snapshot names + /// a different generation. + fn unset_policy_snapshot() -> openshell_core::grpc_client::SettingsPollResult { + let mut snapshot = + settings_poll_result(None, 0, openshell_core::proto::PolicySource::Sandbox); + snapshot.policy_hash = String::new(); + snapshot.config_revision = 7; + snapshot.provider_env_revision = 3; + snapshot + } + + /// A scripted refusal of one startup `UpdateConfig` write. + #[derive(Clone)] + struct SyncRefusal { + code: tonic::Code, + message: &'static str, + } + + impl SyncRefusal { + fn new(code: tonic::Code, message: &'static str) -> Self { + Self { code, message } + } + } + + /// Startup gateway double for the two startup policy writes: the image + /// policy upload and the baseline-path write-back. + /// + /// `sync` consumes the next scripted refusal and fails with its status, + /// wrapped the way the remote client wraps it. While the desired policy is + /// global, it refuses every write the way the gateway does. Otherwise, + /// without a refusal, it stores the payload as the next policy revision and + /// returns the new snapshot, as the gateway does for an accepted write. + /// Every call is recorded, including the generation each report names. + /// Reports for obsolete generations fail with `ABORTED`, as the gateway + /// requires. Tests can install a repair before a refused write returns or + /// script rejection-report errors to exercise retries and fatal failures. + #[derive(Clone)] + struct WriteRefusingStartupGateway { + desired: Arc>, + refusals: Arc>>, + repair_after_refusal: Option, + rejection_report_errors: Arc>>, + calls: UnboundedSender, + } + + #[tonic::async_trait] + impl StartupGateway for WriteRefusingStartupGateway { + async fn snapshot( + &self, + _sandbox: &str, + ) -> Result { + self.calls.send(StartupCall::Snapshot).unwrap(); + Ok(self.desired.lock().unwrap().clone()) + } + async fn provider( + &self, + _id: &str, + ) -> Result { + Ok(startup_provider( + self.desired.lock().unwrap().provider_env_revision, + )) + } + async fn sync( + &self, + _sandbox: &str, + policy: &openshell_core::proto::SandboxPolicy, + _workspace: &str, + ) -> Result { + self.calls.send(StartupCall::Sync).unwrap(); + let mut desired = self.desired.lock().unwrap(); + // The gateway checks for a global policy before it validates the + // payload, so this refusal does not consume a scripted one. + let refusal = if desired.policy_source == openshell_core::proto::PolicySource::Global { + Some(SyncRefusal::new( + tonic::Code::FailedPrecondition, + GLOBAL_POLICY_DIAGNOSTIC, + )) + } else { + self.refusals.lock().unwrap().pop_front() + }; + if let Some(refusal) = refusal { + if let Some(repaired) = &self.repair_after_refusal { + *desired = repaired.clone(); + } + return Err( + openshell_core::grpc_client::grpc_status_error(tonic::Status::new( + refusal.code, + refusal.message, + )) + .wrap_err("failed to sync policy to server"), + ); + } + let version = desired.version + 1; + *desired = settings_poll_result( + Some(policy.clone()), + version, + openshell_core::proto::PolicySource::Sandbox, + ); + Ok(desired.clone()) + } + async fn report( + &self, + _id: &str, + _instance_id: &str, + snapshot: Option<&openshell_core::grpc_client::SettingsPollResult>, + state: openshell_core::proto::ConfigurationAdmissionState, + error: &str, + ) -> Result<()> { + self.calls + .send(StartupCall::Report(StartupReport { + state, + error: error.to_owned(), + generation: snapshot.map(report_generation), + })) + .unwrap(); + if state == openshell_core::proto::ConfigurationAdmissionState::Rejected + && let Some(code) = self.rejection_report_errors.lock().unwrap().pop_front() + { + return Err( + openshell_core::grpc_client::grpc_status_error(tonic::Status::new( + code, + "rejection report failed", + )) + .wrap_err("failed to report sandbox configuration"), + ); + } + if matches!( + state, + openshell_core::proto::ConfigurationAdmissionState::Rejected + | openshell_core::proto::ConfigurationAdmissionState::Accepted + ) && snapshot.map(report_generation) + != Some(report_generation(&self.desired.lock().unwrap())) + { + return Err(openshell_core::grpc_client::grpc_status_error( + tonic::Status::aborted("configuration generation has changed"), + ) + .wrap_err("failed to report sandbox configuration")); + } + Ok(()) + } + } + + /// Waits for startup's next admission report and returns it together with + /// the snapshot and write calls startup made since the previous report. If + /// startup returns first, the test fails with startup's result. + async fn next_startup_report( + calls: &mut tokio::sync::mpsc::UnboundedReceiver, + startup: &mut tokio::task::JoinHandle>, + ) -> (Vec, StartupReport) { + let mut preceding = Vec::new(); + loop { + tokio::select! { + // Drain recorded calls before startup's result so a report sent + // just before startup returned is still observed. + biased; + call = calls.recv() => match call { + Some(StartupCall::Report(report)) => return (preceding, report), + Some(call) => preceding.push(call), + None => panic!("the test gateway call channel closed"), + }, + result = &mut *startup => match result.expect("startup task panicked") { + Ok(_) => panic!("startup accepted a configuration before the expected report"), + Err(error) => panic!( + "startup exited instead of reporting: {}", + startup_error_chain(&error) + ), + }, + } + } + } + + /// Formats startup's error chain on one line. The gRPC status wrapper + /// displays the same text as the status it wraps, so repeats are dropped. + fn startup_error_chain(error: &miette::Report) -> String { + let mut causes: Vec = error.chain().map(ToString::to_string).collect(); + causes.dedup(); + causes.join(": ") + } + + /// Starts a sandbox against a gateway that refuses the startup policy write + /// described by `write` once for each entry of `refusals`, then checks the + /// repair flow the policy documentation promises. On every refused pass, + /// startup reads a fresh snapshot, sends the write again, and reports + /// `Rejected` with the gateway's diagnostic against `initial`, the snapshot + /// the refused write was built from, while it keeps waiting. Once an + /// operator stores `repaired`, the next snapshot is accepted and startup + /// installs that policy. + async fn assert_refused_startup_write_waits_for_repair( + initial: openshell_core::grpc_client::SettingsPollResult, + discovery: ImagePolicyDiscovery, + write: &str, + refusals: Vec, + repaired: openshell_core::proto::SandboxPolicy, + ) { + use openshell_core::proto::{ConfigurationAdmissionState, PolicySource}; + let refused_generation = report_generation(&initial); + let diagnostics: Vec = refusals + .iter() + .map(|refusal| format!("Gateway rejected the {write}: {}", refusal.message)) + .collect(); + let (calls, mut observed) = tokio::sync::mpsc::unbounded_channel(); + let gateway = WriteRefusingStartupGateway { + desired: Arc::new(std::sync::Mutex::new(initial)), + refusals: Arc::new(std::sync::Mutex::new(std::collections::VecDeque::from( + refusals, + ))), + repair_after_refusal: None, + rejection_report_errors: Arc::default(), + calls, + }; + let startup_gateway = gateway.clone(); + let mut startup = tokio::spawn(async move { + load_policy_with_gateway( + Some("sandbox-id".to_string()), + Some("sandbox".to_string()), + Some("http://unused.invalid".to_string()), + None, + None, + &openshell_extension_core::ExtensionCredentialStore::new(), + LocalPolicyIdentity::Required, + Some(discovery), + &startup_gateway, + ) + .await + }); + // A regression that keeps looping without the expected report fails at + // this deadline instead of hanging; the paused clock makes it free. + timeout(Duration::from_mins(1), async { + let (_, registration) = next_startup_report(&mut observed, &mut startup).await; + assert_eq!(registration.state, ConfigurationAdmissionState::Pending); + for (pass, diagnostic) in diagnostics.into_iter().enumerate() { + let (preceding, report) = next_startup_report(&mut observed, &mut startup).await; + assert_eq!( + preceding, + [StartupCall::Snapshot, StartupCall::Sync], + "refused pass {pass}: startup must read a fresh snapshot and send the write again" + ); + assert_eq!( + report, + StartupReport { + state: ConfigurationAdmissionState::Rejected, + error: diagnostic, + generation: Some(refused_generation.clone()), + }, + "refused pass {pass}: the rejection must carry the gateway diagnostic and name the snapshot the refused write was built from" + ); + assert!( + !startup.is_finished(), + "refused pass {pass}: a refused startup write must leave the sandbox waiting for repair" + ); + } + { + let mut desired = gateway.desired.lock().unwrap(); + let version = desired.version + 1; + *desired = + settings_poll_result(Some(repaired.clone()), version, PolicySource::Sandbox); + } + let (_, accepted) = next_startup_report(&mut observed, &mut startup).await; + assert_eq!(accepted.state, ConfigurationAdmissionState::Accepted); + let bundle = (&mut startup) + .await + .unwrap() + .expect("the repaired configuration returns one launch bundle"); + assert_eq!( + bundle.2, + Some(repaired), + "startup must install the repaired policy" + ); + }) + .await + .expect("startup must report every refusal and then accept the repair"); + } + + #[tokio::test(start_paused = true)] + async fn startup_policy_write_refusal_failed_precondition_waits_for_repair() { + // The gateway has no policy, so startup uploads the image policy. + assert_refused_startup_write_waits_for_repair( + unset_policy_snapshot(), + ImagePolicyDiscovery::Policy(Box::new(proto_tcp_policy_fixture())), + "image policy", + vec![SyncRefusal::new( + tonic::Code::FailedPrecondition, + UNATTACHED_PROVIDER_DIAGNOSTIC, + )], + proto_policy_fixture(), + ) + .await; + } + + #[tokio::test(start_paused = true)] + async fn startup_policy_write_refusal_invalid_argument_waits_for_repair() { + assert_refused_startup_write_waits_for_repair( + unset_policy_snapshot(), + ImagePolicyDiscovery::Policy(Box::new(proto_tcp_policy_fixture())), + "image policy", + vec![SyncRefusal::new( + tonic::Code::InvalidArgument, + INVALID_MIDDLEWARE_DIAGNOSTIC, + )], + proto_policy_fixture(), + ) + .await; + } + + #[tokio::test(start_paused = true)] + async fn startup_policy_write_refusal_baseline_write_back_waits_for_repair() { + // The gateway policy has network rules but lacks the proxy baseline + // paths, so startup writes an enriched copy back. The repaired policy + // already carries those paths and needs no write-back. + let mut repaired = proto_tcp_policy_fixture(); + assert!(enrich_proto_baseline_paths(&mut repaired)); + assert_refused_startup_write_waits_for_repair( + settings_poll_result( + Some(proto_tcp_policy_fixture()), + 1, + openshell_core::proto::PolicySource::Sandbox, + ), + ImagePolicyDiscovery::Missing, + "policy update that adds baseline filesystem paths", + vec![SyncRefusal::new( + tonic::Code::FailedPrecondition, + UNATTACHED_PROVIDER_DIAGNOSTIC, + )], + repaired, + ) + .await; + } + + /// Install an operator's repair after the refused write but before its + /// rejection report. The obsolete report must lead straight to a new + /// snapshot, without resending the write or delaying the repaired launch. + async fn assert_startup_repair_before_rejection_refetches( + initial: openshell_core::grpc_client::SettingsPollResult, + discovery: ImagePolicyDiscovery, + repaired: openshell_core::proto::SandboxPolicy, + refusal: SyncRefusal, + write: &str, + ) { + use openshell_core::proto::{ConfigurationAdmissionState, PolicySource}; + let initial_generation = report_generation(&initial); + let repaired_snapshot = settings_poll_result( + Some(repaired.clone()), + initial.version + 1, + PolicySource::Sandbox, + ); + let repaired_generation = report_generation(&repaired_snapshot); + let diagnostic = format!("Gateway rejected the {write}: {}", refusal.message); + let (calls, mut observed) = tokio::sync::mpsc::unbounded_channel(); + let gateway = WriteRefusingStartupGateway { + desired: Arc::new(std::sync::Mutex::new(initial)), + refusals: Arc::new(std::sync::Mutex::new(std::collections::VecDeque::from([ + refusal, + ]))), + repair_after_refusal: Some(repaired_snapshot), + rejection_report_errors: Arc::default(), + calls, + }; + let started = tokio::time::Instant::now(); + let bundle = timeout( + Duration::from_secs(30), + load_policy_with_gateway( + Some("sandbox-id".to_string()), + Some("sandbox".to_string()), + Some("http://unused.invalid".to_string()), + None, + None, + &openshell_extension_core::ExtensionCredentialStore::new(), + LocalPolicyIdentity::Required, + Some(discovery), + &gateway, + ), + ) + .await + .expect("a superseded rejection must not stall startup") + .unwrap_or_else(|error| { + panic!( + "a repair that precedes its rejection report must resume startup: {}", + startup_error_chain(&error) + ) + }); + assert_eq!( + started.elapsed(), + Duration::ZERO, + "a superseded rejection must fetch the repaired generation immediately" + ); + let mut trace = Vec::new(); + while let Ok(call) = observed.try_recv() { + trace.push(call); + } + assert_eq!( + trace, + [ + StartupCall::Snapshot, + StartupCall::Report(StartupReport { + state: ConfigurationAdmissionState::Pending, + error: String::new(), + generation: Some(initial_generation.clone()), + }), + StartupCall::Snapshot, + StartupCall::Sync, + StartupCall::Report(StartupReport { + state: ConfigurationAdmissionState::Rejected, + error: diagnostic, + generation: Some(initial_generation), + }), + StartupCall::Snapshot, + StartupCall::Report(StartupReport { + state: ConfigurationAdmissionState::Accepted, + error: String::new(), + generation: Some(repaired_generation), + }), + ], + "startup must discard the obsolete rejection and validate the repair" + ); + assert_eq!(bundle.2, Some(repaired)); + } + + #[tokio::test(start_paused = true)] + async fn startup_policy_write_refusal_image_repair_before_report_refetches() { + Box::pin(assert_startup_repair_before_rejection_refetches( + unset_policy_snapshot(), + ImagePolicyDiscovery::Policy(Box::new(proto_tcp_policy_fixture())), + proto_policy_fixture(), + SyncRefusal::new( + tonic::Code::FailedPrecondition, + UNATTACHED_PROVIDER_DIAGNOSTIC, + ), + "image policy", + )) + .await; + } + + #[tokio::test(start_paused = true)] + async fn startup_policy_write_refusal_baseline_repair_before_report_refetches() { + let mut repaired = proto_tcp_policy_fixture(); + assert!(enrich_proto_baseline_paths(&mut repaired)); + Box::pin(assert_startup_repair_before_rejection_refetches( + settings_poll_result( + Some(proto_tcp_policy_fixture()), + 1, + openshell_core::proto::PolicySource::Sandbox, + ), + ImagePolicyDiscovery::Missing, + repaired, + SyncRefusal::new(tonic::Code::InvalidArgument, INVALID_MIDDLEWARE_DIAGNOSTIC), + "policy update that adds baseline filesystem paths", + )) + .await; + } + + /// Exercise rejection reporting directly so transport and identity errors + /// cannot be mistaken for policy-write failures or registration failures. + async fn startup_rejection_report_with_errors( + errors: Vec, + ) -> (Result<()>, Vec, Duration, StartupRejectionLog) { + let snapshot = unset_policy_snapshot(); + let (calls, mut observed) = tokio::sync::mpsc::unbounded_channel(); + let gateway = WriteRefusingStartupGateway { + desired: Arc::new(std::sync::Mutex::new(snapshot.clone())), + refusals: Arc::default(), + repair_after_refusal: None, + rejection_report_errors: Arc::new(std::sync::Mutex::new(errors.into())), + calls, + }; + let mut log = StartupRejectionLog::default(); + let started = tokio::time::Instant::now(); + let result = reject_startup_configuration( + &gateway, + &mut log, + "sandbox-id", + "instance-id", + &snapshot, + "policy write refused", + None, + ) + .await; + let mut trace = Vec::new(); + while let Ok(call) = observed.try_recv() { + trace.push(call); + } + assert!(trace.iter().all(|call| matches!( + call, + StartupCall::Report(report) + if report.state == openshell_core::proto::ConfigurationAdmissionState::Rejected + && report.generation == Some(report_generation(&snapshot)) + && report.error == "policy write refused" + ))); + (result, trace, started.elapsed(), log) + } + + #[tokio::test(start_paused = true)] + async fn startup_rejection_report_aborted_after_transient_failure_refetches() { + let (result, trace, elapsed, log) = startup_rejection_report_with_errors(vec![ + tonic::Code::Unavailable, + tonic::Code::Aborted, + ]) + .await; + result.expect("a superseded report must return to reconciliation"); + assert_eq!(trace.len(), 2, "an obsolete report must not be resent"); + assert_eq!(elapsed, Duration::from_secs(1)); + assert!(log.0.is_none(), "an obsolete rejection must not be logged"); + } + + #[tokio::test(start_paused = true)] + async fn startup_rejection_report_transient_failure_retries_and_logs() { + let (result, trace, elapsed, log) = + startup_rejection_report_with_errors(vec![tonic::Code::Unavailable]).await; + result.expect("a transient report failure must retry"); + assert_eq!(trace.len(), 2); + assert_eq!(elapsed, Duration::from_secs(3)); + assert_eq!( + log.0, + Some(( + LoadedPolicyRevision::from_snapshot(&unset_policy_snapshot()), + "policy write refused".to_string(), + )), + "a recorded rejection must retain its diagnostic for log suppression" + ); + } + + #[tokio::test(start_paused = true)] + async fn startup_rejection_report_transient_exhaustion_stays_fatal() { + let (result, trace, elapsed, log) = + startup_rejection_report_with_errors(vec![tonic::Code::Unavailable; 5]).await; + let error = result.expect_err("exhausted report retries must terminate startup"); + assert!(error.to_string().contains("failed after 5 attempts")); + assert_eq!(trace.len(), 5); + assert_eq!(elapsed, Duration::from_secs(11)); + assert!(log.0.is_none()); + } + + #[tokio::test(start_paused = true)] + async fn startup_rejection_report_permanent_failure_stays_fatal() { + for code in [ + tonic::Code::FailedPrecondition, + tonic::Code::InvalidArgument, + tonic::Code::PermissionDenied, + tonic::Code::NotFound, + tonic::Code::Unauthenticated, + ] { + let (result, trace, elapsed, log) = + startup_rejection_report_with_errors(vec![code]).await; + let error = result.expect_err("a permanent report failure must terminate startup"); + assert!(error.chain().any(|cause| { + cause + .downcast_ref::() + .is_some_and(|status| status.code() == code) + })); + assert_eq!(trace.len(), 1, "{code:?} must not be retried"); + assert_eq!(elapsed, Duration::ZERO); + assert!(log.0.is_none()); + } + } + + #[tokio::test(start_paused = true)] + async fn startup_policy_write_refusal_image_upload_outlasts_the_attempt_limit() { + // Startup gives up after five passes that neither accept nor reject a + // configuration. A refused upload is a rejection, so the same refusal + // repeated seven times must keep the sandbox waiting for repair. + assert_refused_startup_write_waits_for_repair( + unset_policy_snapshot(), + ImagePolicyDiscovery::Policy(Box::new(proto_tcp_policy_fixture())), + "image policy", + vec![ + SyncRefusal::new( + tonic::Code::FailedPrecondition, + UNATTACHED_PROVIDER_DIAGNOSTIC + ); + 7 + ], + proto_policy_fixture(), + ) + .await; + } + + #[tokio::test(start_paused = true)] + async fn startup_policy_write_refusal_write_back_outlasts_the_attempt_limit() { + // The same limit applies to a refused baseline-path write-back. + let mut repaired = proto_tcp_policy_fixture(); + assert!(enrich_proto_baseline_paths(&mut repaired)); + assert_refused_startup_write_waits_for_repair( + settings_poll_result( + Some(proto_tcp_policy_fixture()), + 1, + openshell_core::proto::PolicySource::Sandbox, + ), + ImagePolicyDiscovery::Missing, + "policy update that adds baseline filesystem paths", + vec![ + SyncRefusal::new( + tonic::Code::FailedPrecondition, + UNATTACHED_PROVIDER_DIAGNOSTIC + ); + 7 + ], + repaired, + ) + .await; + } + + /// Collects the sandbox log lines that a test's OCSF events render to. + #[derive(Clone, Default)] + struct CapturedLog(Arc>>); + + impl std::io::Write for CapturedLog { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0.lock().unwrap().extend_from_slice(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } + } + + #[tokio::test(start_paused = true)] + async fn startup_policy_write_refusal_logs_a_varying_gateway_message_once() { + use tracing_subscriber::layer::SubscriberExt as _; + use tracing_subscriber::util::SubscriberInitExt as _; + // One policy with two unattached providers is refused with either + // provider's name on each attempt. The snapshot does not change, so the + // sandbox log must record the refusal once, with the first message, + // while every report still carries the message of its own attempt. + let log = CapturedLog::default(); + // Keep two dispatchers alive so tracing consults registered subscribers + // when caching callsite interest. With only one, a parallel test thread + // without a default subscriber can cache `Interest::never` for our events. + let _other_dispatch = tracing::Dispatch::new(tracing::subscriber::NoSubscriber::default()); + let _subscriber = tracing_subscriber::registry() + .with(openshell_ocsf::OcsfShorthandLayer::new(log.clone()).with_non_ocsf(false)) + .set_default(); + let first = SyncRefusal::new( + tonic::Code::FailedPrecondition, + UNATTACHED_PROVIDER_DIAGNOSTIC, + ); + let second = SyncRefusal::new( + tonic::Code::FailedPrecondition, + UNATTACHED_SECOND_PROVIDER_DIAGNOSTIC, + ); + assert_refused_startup_write_waits_for_repair( + unset_policy_snapshot(), + ImagePolicyDiscovery::Policy(Box::new(proto_tcp_policy_fixture())), + "image policy", + vec![first.clone(), second.clone(), first, second], + proto_policy_fixture(), + ) + .await; + let log = String::from_utf8(log.0.lock().unwrap().clone()).unwrap(); + let logged: Vec<&str> = log + .lines() + .filter(|line| line.contains("Gateway rejected the image policy")) + .collect(); + assert_eq!( + logged.len(), + 1, + "an unchanged refusal must be logged once; log:\n{log}" + ); + assert!( + logged[0].contains(UNATTACHED_PROVIDER_DIAGNOSTIC), + "the log must keep the gateway's first message; log:\n{log}" + ); + } + + #[tokio::test(start_paused = true)] + async fn startup_policy_write_refusal_global_policy_skips_baseline_write_back() { + use openshell_core::proto::{ConfigurationAdmissionState, PolicySource}; + // The gateway serves a global policy that has network rules but lacks + // the proxy baseline paths. It refuses every sandbox policy write while + // that policy is active, so startup must add the paths locally and + // install the policy without writing it back. + let mut global = + settings_poll_result(Some(proto_tcp_policy_fixture()), 1, PolicySource::Global); + global.global_policy_version = 1; + let global_generation = report_generation(&global); + let mut enriched = proto_tcp_policy_fixture(); + assert!(enrich_proto_baseline_paths(&mut enriched)); + let (calls, mut observed) = tokio::sync::mpsc::unbounded_channel(); + let gateway = WriteRefusingStartupGateway { + desired: Arc::new(std::sync::Mutex::new(global)), + refusals: Arc::default(), + repair_after_refusal: None, + rejection_report_errors: Arc::default(), + calls, + }; + let bundle = timeout( + Duration::from_mins(1), + load_policy_with_gateway( + Some("sandbox-id".to_string()), + Some("sandbox".to_string()), + Some("http://unused.invalid".to_string()), + None, + None, + &openshell_extension_core::ExtensionCredentialStore::new(), + LocalPolicyIdentity::Required, + Some(ImagePolicyDiscovery::Missing), + &gateway, + ), + ) + .await + .expect("startup must not wait for repair under a global policy") + .unwrap_or_else(|error| { + panic!( + "startup exited under a global policy: {}", + startup_error_chain(&error) + ) + }); + let mut trace = Vec::new(); + while let Ok(call) = observed.try_recv() { + trace.push(call); + } + let report = |state| { + StartupCall::Report(StartupReport { + state, + error: String::new(), + generation: Some(global_generation.clone()), + }) + }; + assert_eq!( + trace, + [ + StartupCall::Snapshot, + report(ConfigurationAdmissionState::Pending), + StartupCall::Snapshot, + report(ConfigurationAdmissionState::Accepted), + ], + "startup must not write a global policy back as a sandbox policy" + ); + assert_eq!( + bundle.2, + Some(enriched), + "startup installs the global policy with the baseline paths added" + ); + let LoadedPolicyOrigin::Gateway { + revision: Some(revision), + .. + } = bundle.4 + else { + panic!("startup must bind the global policy to its gateway revision"); + }; + assert_eq!(revision.policy_source, PolicySource::Global); + } + + #[tokio::test(start_paused = true)] + async fn startup_policy_write_refusal_permanent_codes_still_exit() { + use openshell_core::proto::{ConfigurationAdmissionState, PolicySource}; + // No policy or provider change repairs these refusals, so startup + // keeps exiting instead of holding the sandbox in its repair window. + // Both writes are checked: the image policy upload to a gateway with + // no policy, and the write-back of a stored policy that lacks the + // proxy baseline paths. + let writes = [ + ( + "image policy upload", + unset_policy_snapshot(), + ImagePolicyDiscovery::Policy(Box::new(proto_tcp_policy_fixture())), + ), + ( + "baseline write-back", + settings_poll_result(Some(proto_tcp_policy_fixture()), 1, PolicySource::Sandbox), + ImagePolicyDiscovery::Missing, + ), + ]; + for (write, initial, discovery) in writes { + for code in [ + tonic::Code::PermissionDenied, + tonic::Code::NotFound, + tonic::Code::Unauthenticated, + ] { + let (calls, mut observed) = tokio::sync::mpsc::unbounded_channel(); + let gateway = WriteRefusingStartupGateway { + desired: Arc::new(std::sync::Mutex::new(initial.clone())), + refusals: Arc::new(std::sync::Mutex::new(std::collections::VecDeque::from([ + SyncRefusal::new(code, "sandbox caller cannot write this policy"), + ]))), + repair_after_refusal: None, + rejection_report_errors: Arc::default(), + calls, + }; + let result = timeout( + Duration::from_mins(1), + load_policy_with_gateway( + Some("sandbox-id".to_string()), + Some("sandbox".to_string()), + Some("http://unused.invalid".to_string()), + None, + None, + &openshell_extension_core::ExtensionCredentialStore::new(), + LocalPolicyIdentity::Required, + Some(discovery.clone()), + &gateway, + ), + ) + .await + .expect("a permanent write error must end startup"); + let Err(error) = result else { + panic!("{write}: startup accepted a configuration after {code:?}"); + }; + assert!( + startup_error_chain(&error).contains("sandbox caller cannot write this policy"), + "{write}, {code:?}: {}", + startup_error_chain(&error) + ); + let mut trace = Vec::new(); + while let Ok(call) = observed.try_recv() { + trace.push(call); + } + assert!( + !trace.iter().any(|call| matches!( + call, + StartupCall::Report(report) + if report.state == ConfigurationAdmissionState::Rejected + )), + "{write}, {code:?} is not a configuration rejection; calls: {trace:?}" + ); + } + } + } + + #[test] + fn startup_policy_write_refusal_bounds_the_gateway_message() { + let refused = openshell_core::grpc_client::grpc_status_error( + tonic::Status::invalid_argument(format!("bad\nrule {}", "x".repeat(600))), + ) + .wrap_err("failed to sync policy to server"); + // The log key leaves out the gateway's message, so a refusal whose + // wording changes between attempts is logged once per snapshot. + assert_eq!( + startup_write_rejection("image policy", &refused), + Some(StartupWriteRejection { + diagnostic: format!( + "Gateway rejected the image policy: badrule {}", + "x".repeat(504) + ), + log_key: "Gateway rejected the image policy (InvalidArgument)".to_owned(), + }) + ); + // Exhausted transient retries and connection failures carry no status + // and stay fatal. + assert_eq!( + startup_write_rejection( + "image policy", + &miette::miette!("Image policy synchronization failed after 5 attempts") + ), + None + ); + } + fn startup_provider(revision: u64) -> openshell_core::grpc_client::ProviderEnvironmentResult { openshell_core::grpc_client::ProviderEnvironmentResult { files: std::collections::HashMap::new(), diff --git a/docs/how-it-works/policies/default-policy.mdx b/docs/how-it-works/policies/default-policy.mdx index b682c14437..3f148dcf70 100644 --- a/docs/how-it-works/policies/default-policy.mdx +++ b/docs/how-it-works/policies/default-policy.mdx @@ -75,7 +75,9 @@ policy has no `filesystem_policy` section, OpenShell creates one with `include_workdir: true`. The sandbox saves the enriched filesystem policy as a new revision, so the -added paths appear in `openshell policy get --base`. OpenShell can reject a +added paths appear in `openshell policy get --base`. While a global policy is +active, the sandbox adds the paths without saving a revision, so they do not +appear in the policy views. OpenShell can reject a replacement policy that removes filesystem paths, so keep the added paths when you replace the complete policy. diff --git a/docs/how-it-works/policies/manage-policies.mdx b/docs/how-it-works/policies/manage-policies.mdx index 151c10414c..f7416a62fc 100644 --- a/docs/how-it-works/policies/manage-policies.mdx +++ b/docs/how-it-works/policies/manage-policies.mdx @@ -366,6 +366,8 @@ check the sandbox log for the specific error: openshell sandbox get my-sandbox --output json ``` +The same repair window applies when the gateway refuses an image-policy upload or a policy update that adds baseline filesystem paths with `FAILED_PRECONDITION` or `INVALID_ARGUMENT`. After the gateway acknowledges the rejection report, startup waits two seconds, reads a fresh configuration, and retries. If your repair reaches the gateway before that report, startup immediately reads the repaired configuration. The sandbox log records the gateway's reason; repeated refusals of the same write, status code, and configuration are logged once. Authentication and authorization failures still stop startup. + You have 300 seconds to fix the configuration. Replace the policy with `openshell policy set`, or fix the provider configuration. Until the workload first starts, you can also change filesystem, Landlock, and process settings. From ffcbe6280ceba212ab95253897da9bf2d909da0f Mon Sep 17 00:00:00 2001 From: Fede Kamelhar <209537060+fede-kamel@users.noreply.github.com> Date: Thu, 1 Oct 2026 16:11:49 +0000 Subject: [PATCH 27/33] fix(cli): start sandbox exec without waiting for piped stdin EOF (#4006) With a non-terminal stdin, sandbox exec read stdin to EOF before it sent the exec request. A pipe that never closes (CI runners, supervisors, agent harnesses) blocked the CLI forever in read(2) without the gateway ever seeing the request, and a slow producer delayed the command until EOF. Collect piped stdin on a detached reader thread for at most 200 ms. Input that reaches EOF within that window still travels in the single request that older gateways need. If the pipe is still open, start the command through the streaming RPC and forward the collected prefix plus the rest of stdin as it arrives, closing remote stdin at EOF. The 4 MiB cap covers the prefix and the streamed remainder together. Closes #3993 Signed-off-by: Federico Kamelhar --- crates/openshell-cli/src/run.rs | 307 +++++++++++++++++++---- docs/how-it-works/sandboxes/overview.mdx | 8 +- 2 files changed, 264 insertions(+), 51 deletions(-) diff --git a/crates/openshell-cli/src/run.rs b/crates/openshell-cli/src/run.rs index 02c806c421..c9dd405f8b 100644 --- a/crates/openshell-cli/src/run.rs +++ b/crates/openshell-cli/src/run.rs @@ -1852,6 +1852,86 @@ fn local_terminal_size() -> Option<(u32, u32)> { const MAX_EXEC_REQUEST_BYTES: usize = 1024 * 1024; const MAX_EXEC_STDIN_BYTES: usize = 4 * 1024 * 1024; +/// How long `sandbox exec` waits for piped stdin to reach EOF before it starts +/// the command and streams the rest of the input as it arrives. +/// +/// Small pipes such as `echo x | openshell sandbox exec ...` close well within +/// this window and keep using the single-request path that older gateways +/// need. A pipe that stays open (a CI runner, a supervisor, a harness that +/// never closes stdin) must not block the command: after the grace period the +/// command starts and stdin is forwarded until EOF or until the command exits. +const EXEC_STDIN_UNARY_GRACE: Duration = Duration::from_millis(200); + +/// Piped stdin as collected by [`collect_piped_stdin`]. +enum PipedStdin { + /// stdin reached EOF within the grace period; `prefix` holds all of it. + Complete(Vec), + /// stdin is still open. `prefix` is what arrived so far; `rest` delivers + /// the remaining chunks until EOF. + Open { + prefix: Vec, + rest: tokio::sync::mpsc::Receiver>>, + }, +} + +/// Read a pipe on a detached OS thread and hand its bytes over in chunks. +/// +/// A plain `std::thread` rather than `spawn_blocking`, so runtime shutdown +/// never waits on a thread parked in `read(2)`. The thread exits at EOF, on a +/// read error, or when the receiver is dropped. +fn spawn_piped_stdin_reader( + mut reader: impl Read + Send + 'static, +) -> tokio::sync::mpsc::Receiver>> { + let (tx, rx) = tokio::sync::mpsc::channel::>>(64); + std::thread::spawn(move || { + let mut buf = [0u8; 4096]; + loop { + match reader.read(&mut buf) { + Ok(0) => return, + Err(error) if error.kind() == ErrorKind::Interrupted => {} + Err(error) => { + let _ = tx.blocking_send(Err(error)); + return; + } + Ok(n) => { + if tx.blocking_send(Ok(buf[..n].to_vec())).is_err() { + return; + } + } + } + } + }); + rx +} + +/// Collect piped stdin until EOF or until `grace` elapses, whichever comes +/// first. Input beyond `limit` bytes is rejected with the upload hint. +async fn collect_piped_stdin( + mut rx: tokio::sync::mpsc::Receiver>>, + grace: Duration, + limit: usize, +) -> Result { + let deadline = tokio::time::Instant::now() + grace; + let mut prefix = Vec::new(); + loop { + match tokio::time::timeout_at(deadline, rx.recv()).await { + Ok(Some(Ok(chunk))) => { + prefix.extend_from_slice(&chunk); + if prefix.len() > limit { + return Err(piped_stdin_limit_error()); + } + } + Ok(Some(Err(error))) => return Err(error).into_diagnostic(), + Ok(None) => return Ok(PipedStdin::Complete(prefix)), + Err(_elapsed) => return Ok(PipedStdin::Open { prefix, rest: rx }), + } + } +} + +fn piped_stdin_limit_error() -> miette::Report { + miette::miette!("piped stdin exceeds the 4 MiB limit; use `sandbox upload` for larger input") +} + /// Execute a command in a running sandbox via gRPC, streaming output to the terminal. /// /// Returns the remote command's exit code, or an error if the event stream @@ -1902,24 +1982,24 @@ pub async fn sandbox_exec_grpc( // interactive RPC closes the SSH channel when stdin reaches EOF. Retain // the existing 4 MiB input cap because the supervisor's process stdin // queue is unbounded; larger input should use file upload instead. - let stdin_prefix = if stdin_is_terminal { - Vec::new() + // + // Never block on stdin EOF before starting the command: a pipe that stays + // open (CI runners, harnesses) would otherwise hang the exec forever + // without the gateway ever seeing the request. After a short grace period + // the command starts and the remaining input streams until EOF. + let (stdin_prefix, stdin_rest) = if stdin_is_terminal { + (Vec::new(), None) } else { - tokio::task::spawn_blocking(|| { - let mut prefix = Vec::new(); - std::io::stdin() - .take((MAX_EXEC_STDIN_BYTES + 1) as u64) - .read_to_end(&mut prefix) - .into_diagnostic()?; - if prefix.len() > MAX_EXEC_STDIN_BYTES { - return Err(miette::miette!( - "piped stdin exceeds the 4 MiB limit; use `sandbox upload` for larger input" - )); - } - Ok::<_, miette::Report>(prefix) - }) - .await - .into_diagnostic()?? + match collect_piped_stdin( + spawn_piped_stdin_reader(std::io::stdin()), + EXEC_STDIN_UNARY_GRACE, + MAX_EXEC_STDIN_BYTES, + ) + .await? + { + PipedStdin::Complete(prefix) => (prefix, None), + PipedStdin::Open { prefix, rest } => (prefix, Some(rest)), + } }; let (cols, rows) = if tty { @@ -1952,7 +2032,10 @@ pub async fn sandbox_exec_grpc( "exec command or environment exceeds the gateway's 1 MiB message limit" )); } - if (tty && stdin_is_terminal) || request.encoded_len() > MAX_EXEC_REQUEST_BYTES { + if (tty && stdin_is_terminal) + || stdin_rest.is_some() + || request.encoded_len() > MAX_EXEC_REQUEST_BYTES + { return sandbox_exec_streaming_grpc( client, &sandbox, @@ -1964,6 +2047,7 @@ pub async fn sandbox_exec_grpc( tty, stdin_is_terminal, std::mem::take(&mut request.stdin), + stdin_rest, ) .await; } @@ -2350,6 +2434,7 @@ async fn sandbox_exec_streaming_grpc( tty: bool, stdin_is_terminal: bool, stdin_prefix: Vec, + stdin_rest: Option>>>, ) -> Result { #[cfg(unix)] use openshell_core::proto::ExecSandboxWindowResize; @@ -2407,42 +2492,88 @@ async fn sandbox_exec_streaming_grpc( // closes (blocking_send returns Err) or stdin hits EOF. let stdin_tx = input_tx.clone(); let (stdin_result_tx, mut stdin_result_rx) = tokio::sync::oneshot::channel(); - std::thread::spawn(move || { - let mut stdin = std::io::stdin().lock(); - let mut buf = [0u8; 4096]; - let result = (|| { - for chunk in stdin_prefix.chunks(buf.len()) { - if stdin_tx - .blocking_send(ExecSandboxInput { - payload: Some(exec_sandbox_input::Payload::Stdin(chunk.to_vec())), - }) - .is_err() - { - return Ok(()); + if let Some(mut rest) = stdin_rest { + // Piped stdin that was still open when the command started: the + // reader thread from `spawn_piped_stdin_reader` already owns stdin, + // so forward its chunks here after the collected prefix. The 4 MiB + // cap covers the prefix and the streamed remainder together. + tokio::spawn(async move { + let mut forwarded = 0usize; + let result = async { + for chunk in stdin_prefix.chunks(4096) { + forwarded += chunk.len(); + if stdin_tx + .send(ExecSandboxInput { + payload: Some(exec_sandbox_input::Payload::Stdin(chunk.to_vec())), + }) + .await + .is_err() + { + return Ok(()); + } } + while let Some(chunk) = rest.recv().await { + let chunk = chunk?; + forwarded += chunk.len(); + if forwarded > MAX_EXEC_STDIN_BYTES { + return Err(std::io::Error::new( + ErrorKind::InvalidInput, + piped_stdin_limit_error().to_string(), + )); + } + if stdin_tx + .send(ExecSandboxInput { + payload: Some(exec_sandbox_input::Payload::Stdin(chunk)), + }) + .await + .is_err() + { + return Ok(()); + } + } + Ok(()) } - loop { - match stdin.read(&mut buf) { - Ok(0) => return Ok(()), - Err(error) if error.kind() == ErrorKind::Interrupted => {} - Err(error) => return Err(error), - Ok(n) => { - if stdin_tx - .blocking_send(ExecSandboxInput { - payload: Some(exec_sandbox_input::Payload::Stdin( - buf[..n].to_vec(), - )), - }) - .is_err() - { - return Ok(()); + .await; + let _ = stdin_result_tx.send(result); + }); + } else { + std::thread::spawn(move || { + let mut stdin = std::io::stdin().lock(); + let mut buf = [0u8; 4096]; + let result = (|| { + for chunk in stdin_prefix.chunks(buf.len()) { + if stdin_tx + .blocking_send(ExecSandboxInput { + payload: Some(exec_sandbox_input::Payload::Stdin(chunk.to_vec())), + }) + .is_err() + { + return Ok(()); + } + } + loop { + match stdin.read(&mut buf) { + Ok(0) => return Ok(()), + Err(error) if error.kind() == ErrorKind::Interrupted => {} + Err(error) => return Err(error), + Ok(n) => { + if stdin_tx + .blocking_send(ExecSandboxInput { + payload: Some(exec_sandbox_input::Payload::Stdin( + buf[..n].to_vec(), + )), + }) + .is_err() + { + return Ok(()); + } } } } - } - })(); - let _ = stdin_result_tx.send(result); - }); + })(); + let _ = stdin_result_tx.send(result); + }); + } // SIGWINCH handler: forward terminal resize events. #[cfg(unix)] @@ -8206,4 +8337,82 @@ mod tests { let log = log_line("OCSF", "ocsf", message, "sandbox", &[]); assert!(format_log_line(&log).ends_with(message)); } + + use std::io::Write as _; + use std::time::{Duration, Instant}; + + fn exec_stdin_runtime() -> tokio::runtime::Runtime { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("runtime") + } + + #[test] + fn piped_stdin_that_closes_quickly_goes_in_one_request() { + let (reader, mut writer) = std::io::pipe().expect("pipe"); + writer.write_all(b"hi\n").unwrap(); + drop(writer); + let collected = exec_stdin_runtime().block_on(super::collect_piped_stdin( + super::spawn_piped_stdin_reader(reader), + Duration::from_secs(5), + super::MAX_EXEC_STDIN_BYTES, + )); + match collected.expect("collect") { + super::PipedStdin::Complete(prefix) => assert_eq!(prefix, b"hi\n"), + super::PipedStdin::Open { .. } => panic!("closed pipe must complete"), + } + } + + #[test] + fn piped_stdin_that_stays_open_does_not_block_the_command() { + let (reader, mut writer) = std::io::pipe().expect("pipe"); + writer.write_all(b"early").unwrap(); + let grace = Duration::from_millis(100); + let started = Instant::now(); + let runtime = exec_stdin_runtime(); + let collected = runtime.block_on(super::collect_piped_stdin( + super::spawn_piped_stdin_reader(reader), + grace, + super::MAX_EXEC_STDIN_BYTES, + )); + let elapsed = started.elapsed(); + assert!(elapsed >= grace, "must wait the grace period: {elapsed:?}"); + assert!( + elapsed < Duration::from_secs(3), + "must not wait for EOF: {elapsed:?}" + ); + let super::PipedStdin::Open { prefix, mut rest } = collected.expect("collect") else { + panic!("an open pipe must start the command before EOF"); + }; + assert_eq!(prefix, b"early"); + // The remainder keeps flowing after the command has started. + writer.write_all(b"late").unwrap(); + drop(writer); + let next = runtime + .block_on(rest.recv()) + .expect("late chunk") + .expect("read"); + assert_eq!(next, b"late"); + assert!( + runtime.block_on(rest.recv()).is_none(), + "EOF closes the channel" + ); + } + + #[test] + fn piped_stdin_over_the_limit_is_rejected_with_the_upload_hint() { + let (reader, mut writer) = std::io::pipe().expect("pipe"); + writer.write_all(&[0u8; 8]).unwrap(); + drop(writer); + let error = exec_stdin_runtime() + .block_on(super::collect_piped_stdin( + super::spawn_piped_stdin_reader(reader), + Duration::from_secs(5), + 4, + )) + .err() + .expect("over the limit must fail"); + assert!(error.to_string().contains("sandbox upload"), "{error}"); + } } diff --git a/docs/how-it-works/sandboxes/overview.mdx b/docs/how-it-works/sandboxes/overview.mdx index 8194b0df5b..db157750b5 100644 --- a/docs/how-it-works/sandboxes/overview.mdx +++ b/docs/how-it-works/sandboxes/overview.mdx @@ -325,8 +325,12 @@ The CLI sends small piped input in one request for compatibility with older gateways. It streams larger input in bounded frames, including for commands without a TTY, so input is not limited by the gateway's per-message request size. Piped input is limited to 4 MiB; use `sandbox upload` for larger files. -The CLI closes remote stdin when the pipe reaches EOF and continues -reading command output until the command finishes. +The CLI waits at most 200 ms for piped stdin to reach EOF. If the pipe is +still open after that, for example under a CI runner or a harness that never +closes stdin, the command starts anyway and the remaining input streams to it +as it arrives. The CLI closes remote stdin when the pipe reaches EOF and +continues reading command output until the command finishes. Redirect stdin +from `/dev/null` when a command needs no input. The command's exit code is propagated to the CLI, so `exec` works in scripts that check return codes. Large stdout and stderr streams are delivered before a successful exit. A slow From 6e369f23964ad7c5a1cbc243340cfb723dd263f4 Mon Sep 17 00:00:00 2001 From: "John T. Myers" <9696606+johntmyers@users.noreply.github.com> Date: Thu, 1 Oct 2026 16:20:35 +0000 Subject: [PATCH 28/33] chore(agents): simplify contributor instructions and workflows (#3987) * chore(agents): simplify contributor instructions and workflows Closes #3980 Signed-off-by: John Myers * docs(contributing): scope verification to affected components Signed-off-by: John Myers * docs(contributing): standardize issue branch naming Signed-off-by: John Myers --------- Signed-off-by: John Myers Co-authored-by: John Myers --- .agents/skills/build-from-issue/SKILL.md | 760 +----------------- .agents/skills/create-github-issue/SKILL.md | 34 +- .agents/skills/create-github-pr/SKILL.md | 56 +- .agents/skills/create-spike/SKILL.md | 315 +------- .agents/skills/fix-security-issue/SKILL.md | 320 +------- .agents/skills/review-security-issue/SKILL.md | 193 +---- .agents/skills/sync-agent-infra/SKILL.md | 234 +----- .agents/skills/triage-issue/SKILL.md | 253 +----- .agents/skills/update-docs/SKILL.md | 2 +- .github/ISSUE_TEMPLATE/bug_report.yml | 26 +- .github/ISSUE_TEMPLATE/feature_request.yml | 12 +- .github/PULL_REQUEST_TEMPLATE.md | 12 +- .github/workflows/stale.yml | 2 +- AGENTS.md | 53 +- CONTRIBUTING.md | 192 +---- README.md | 2 +- docs/CONTRIBUTING.mdx | 2 +- 17 files changed, 189 insertions(+), 2279 deletions(-) diff --git a/.agents/skills/build-from-issue/SKILL.md b/.agents/skills/build-from-issue/SKILL.md index 4ec54e61a4..bdcf02e8a2 100644 --- a/.agents/skills/build-from-issue/SKILL.md +++ b/.agents/skills/build-from-issue/SKILL.md @@ -1,760 +1,34 @@ --- name: build-from-issue -description: Given a GitHub issue number, plan and implement the work described in the issue. Supports direct user requests and unattended queue processing through the `agent:*` workflow labels. Includes tests, documentation updates, and PR creation. Trigger keywords - build from issue, implement issue, work on issue, build issue, start issue. +description: Plan and implement work described in a GitHub issue, including verification, documentation, and a PR that closes the issue. metadata: internal: true --- # Build From Issue -Plan, iterate on feedback, and implement work described in a GitHub issue. +Use a specific GitHub issue to plan and implement a scoped change. Direct user instructions authorize the phase requested. Planning alone does not authorize implementation. For unattended work, inspect the current `state:*` label descriptions, maintainer assignments, and issue comments to infer the authorized phase. Proceed only when those records authorize the phase; do not assume every state permits implementation. -This skill operates as a stateful workflow — it can be run repeatedly against the same issue. Each invocation inspects the issue's labels, plan comment, and conversation history to determine the correct next action. +## Inspect the issue -## Prerequisites +1. Run `gh issue view --json number,title,body,state,labels,comments,assignees` and inspect the repository's current `state:*` labels and descriptions with `gh label list`. Infer whether triage, validation, and human acceptance have happened. Do not hard-code label names or change disposition as part of building. +2. Read the issue, comments, linked PRs, and current code. Check for an active owner or implementation. If the issue concerns a vulnerability, use `review-security-issue` and `fix-security-issue` instead. +3. Confirm that the User Story attests to the human operator's first-hand OpenShell use and gives a specific use case. If the issue lacks this, ask the operator before proceeding with planning or implementation. For a bug, require reproduction using only an OpenShell deployment; do not install third-party tools solely to demonstrate the problem. +4. If the user's direct request starts before the normal issue disposition, briefly report the discrepancy and continue with the authorized phase. Stop only when information needed to do the work is actually unavailable or a conflicting owner needs resolution. -- The `gh` CLI must be authenticated (`gh auth status`) -- You must be in a git repository with a GitHub remote +## Plan -## Invocation and Authorization +Identify the user-visible outcome, affected code, alternatives, tests, and documentation. For configuration, CLI, SDK, or other UX changes, include notional commands, configuration, or API examples so a human can review the proposed interaction. Consider existing extensibility points such as middleware, interceptors, and providers. Prefer an applicable extension when it satisfies the use case; the need to run another service alone does not disqualify it. -This skill supports two invocation modes: +Use a single issue comment beginning with `> **🏗️ build-plan**` when a plan should be recorded on GitHub. On later invocations, read that comment and any newer human feedback before taking action. Respond to unanswered feedback with a comment beginning `> **🏗️ build-from-issue-agent**`; update the existing plan comment in place when the design changes. Do not repeat a completed plan or open a second PR. Distinguish technical findings from product decisions. If the user requested only a plan, stop after the plan is available for review. -- **Direct mode:** A user explicitly asks the agent to plan or implement a specific issue. The request itself authorizes the requested phase; the corresponding `agent:*` request label is not required. -- **Queue mode:** An always-on or unattended agent scans for work without a live user directing it to a specific issue. In this mode, `agent:plan-requested` authorizes planning and `agent:implementation-requested` authorizes implementation. +## Implement -A direct request authorizes only what it says. A request to review or plan does not authorize implementation. A request to build, implement, or work on an issue authorizes both the planning needed to perform the work and implementation unless the user asks to stop after planning. +1. Check the current branch and working tree. Preserve unrelated work. Create a branch or worktree as needed, with the branch named `/-/`. Use a Conventional Commits type for ``. +2. Implement the smallest coherent change that fulfills the acceptance criteria. Update relevant skills when behavior or commands change. Keep published documentation minimal: explain exactly what users need, avoid duplication across pages, and omit internal details with no user impact. +3. Add meaningful tests for changed behavior and follow the verification guidance in `CONTRIBUTING.md`. Select format, lint, compile or type checks, and tests for affected components and their dependencies. Run the relevant E2E lane for infrastructure, sandbox, or policy changes. Guidance and template edits need applicable Markdown, YAML, link, and consistency checks. Do not require full Rust, SDK, or repository CI solely because a commit or PR is being created; broaden checks only for a concrete remaining risk or failed check. +4. Review the diff, use a signed-off Conventional Commit, and prepare a PR following `create-github-pr`. -The two request labels remain human-only queue controls. Under **no circumstances** should this skill or any agent apply them, ask to apply them, or suggest automating their application. +Every PR must have its own existing issue and use `Closes #` in its Related Issue section. For work needing multiple PRs, split the scope into a closable issue per PR. A high-level issue may track those issues but should not be closed by an incomplete PR. Report the implementation, verification, and any remaining limitation in the PR description, rather than copying earlier issue diagnostics. -In direct mode, issue lifecycle and `agent:*` workflow labels are advisory rather than gates. Inspect the labels and warn the user about each expected label that is missing or any lifecycle label that indicates the normal workflow is incomplete, then continue with the requested phase. Do not ask the user to fix the labels first. A direct request does not change the issue's disposition or make the labels accurate; it only authorizes the requested work. - -If direct work begins on an issue that was not already in the label-driven workflow, do not introduce `agent:in-progress` or `agent:pr-opened` solely for that invocation. If a matching request label is present, preserve the existing label transitions so unattended agents can track the workflow. - -## Agent Comment Markers - -This skill uses two distinct markers to identify its comments: - -### Plan marker - -The implementation plan lives in a **single comment** that is edited in place as the plan evolves. It is identified by this marker on its first line: - -``` -> **🏗️ build-plan** -``` - -### Conversation marker - -All other comments (responses to human feedback, status updates, PR announcements) use this marker: - -``` -> **🏗️ build-from-issue-agent** -``` - -These markers distinguish agent comments from human comments and from other skills (e.g., `🔒 security-review-agent`, `🔧 security-fix-agent`). - -## State Machine Overview - -Each invocation follows this decision tree: - -``` -Fetch issue + comments - │ - ├─ topic:security present? - │ → Route to review-security-issue or fix-security-issue; STOP - │ - ├─ Direct mode + expected lifecycle or agent-workflow labels missing/incomplete? - │ → Warn which labels are missing or incomplete; continue with the requested phase - │ - ├─ Queue mode + triage incomplete, awaiting information, or awaiting human disposition? - │ → Report the blocking state and STOP - │ - ├─ No plan comment and no direct planning request and agent:plan-requested absent? - │ → No request for agent planning; STOP - │ - ├─ No plan comment + direct planning request or agent:plan-requested present? - │ → Generate plan via principal-engineer-reviewer - │ → Post plan comment - │ → Advance labels only for a label-driven invocation - │ → Continue if the direct request also authorized implementation; otherwise STOP - │ - ├─ Plan exists + new human comments since last agent response? - │ → Respond to each comment (quote context, address feedback) - │ → Update the plan comment if feedback requires plan changes - │ → STOP - │ - ├─ Plan exists + direct implementation request or 'agent:implementation-requested' label? - │ → Run scope check (warn if high complexity) - │ → Check for conflicting branches/PRs - │ → BUILD (Steps 6–14) - │ - ├─ 'agent:in-progress' label present? - │ → Detect existing branch and resume if possible - │ → Otherwise report current state - │ - ├─ 'agent:pr-opened' label present? - │ → Report that PR already exists, link to it - │ → STOP - │ - └─ Plan exists + no new comments + neither a direct implementation request nor 'agent:implementation-requested'? - → Report: "Plan is posted and awaiting review. No new comments to address." - → STOP -``` - -## Step 1: Fetch the Issue - -The user provides an issue ID (e.g., `#42` or `42`). Strip any leading `#` and fetch: - -```bash -gh issue view --json number,title,body,state,labels,author -``` - -If the issue is closed, report that and stop. - -If `topic:security` is present, stop. General build agents must not plan or implement security issues. Route planning/review to `review-security-issue` and authorized remediation to `fix-security-issue`. - -In queue mode, stop before planning on `state:triage-needed` or `state:needs-info`, and stop on `state:validated` without roadmap placement. Require `state:accepted` or roadmap placement before queue work proceeds. If no plan exists, require `agent:plan-requested`; require `agent:implementation-requested` before queue-mode implementation. - -In direct mode, inspect the same expected workflow state but do not stop because a lifecycle or agent-workflow label is absent or incomplete. Before continuing, warn the user with the specific discrepancy, for example: - -> "Issue #42 is missing `state:accepted` or roadmap placement and `agent:implementation-requested`. Those labels are expected in the queued workflow, but your direct request authorizes implementation, so I am continuing without changing them." - -If `state:triage-needed`, `state:needs-info`, or `state:validated` is present, name that state in the warning and explain what it normally means. Continue unless the issue lacks information that is actually necessary to perform the requested work; in that case, report the concrete missing information rather than treating the label itself as the blocker. - -Never add or remove `state:accepted`, either human request label, or the `roadmap` label. - -## Step 2: Fetch and Classify Comments - -Fetch all comments: - -```bash -gh issue view --json comments --jq '.comments[] | {id: .id, body: .body, author: .author.login, createdAt: .createdAt, updatedAt: .updatedAt}' -``` - -Classify each comment into one of: - -- **Plan comment**: body starts with `> **🏗️ build-plan**` -- **Agent comment**: body starts with `> **🏗️ build-from-issue-agent**` -- **Human comment**: everything else (not agent-marked) - -Record the plan comment's `id` (needed for editing via API) and its `updatedAt` timestamp. - -## Step 3: Determine Action - -Using the state machine above, determine what to do based on: - -1. Whether a plan comment exists -2. Whether there are human comments newer than the last agent comment (plan or conversation) -3. Whether this is direct mode and which phase the user requested -4. Which lifecycle and agent-workflow labels are present (`state:*`, `agent:plan-requested`, `agent:plan-ready`, `agent:implementation-requested`, `agent:in-progress`, and `agent:pr-opened`) and which discrepancies require a direct-mode warning - -Follow the appropriate branch below. - ---- - -## Branch A: Generate the Plan - -If no plan comment exists, generate one when the user directly requested planning or implementation, or when `agent:plan-requested` is present. Otherwise report that no one has requested agent planning and stop. - -### A1: Analyze the Issue with Principal Engineer Reviewer - -Pass the issue title, description, labels, and any relevant code references to the `principal-engineer-reviewer` sub-agent. Use the Task tool: - -``` -Task tool with subagent_type="principal-engineer-reviewer" -``` - -In the prompt, instruct the reviewer to: - -1. Read the issue's user story and identify what needs to change in the codebase. Treat reporter diagnostics or solution ideas as optional context, not as authoritative or current analysis. -2. Map the requirements to existing code — read the relevant source files. -3. Determine the **issue type** — one of: `feat` (new feature), `fix` (bug fix), `refactor`, `chore`, `perf`, `docs`. -4. Propose the minimal set of changes that satisfies the requirements. -5. Sequence the work so each step is independently testable. -6. Identify what tests are needed (unit, integration, e2e) and where they should live. -7. Assess **complexity** on a scale: - - **Low**: Isolated change, < 3 files, clear path forward - - **Medium**: Multiple files/components, some design decisions, but well-scoped - - **High**: Cross-cutting changes, architectural decisions needed, significant unknowns -8. Call out risks, unknowns, and decisions that need stakeholder input. -9. Assess **gateway config documentation impact** — if the change adds, removes, renames, or changes defaults for gateway TOML keys or driver-specific config options, the plan must include an update to `docs/how-it-works/gateways/configuration.mdx`. If the change is surfaced through Helm or a compute-driver overview, also include `docs/how-it-works/sandboxes/runtimes.mdx` or the relevant deployment docs. -10. Assess **LSM compatibility** — if the change touches process identity, `/proc` filesystem access, binary execution, or inter-process visibility, flag whether it will behave differently on hosts running SELinux (enforcing) or AppArmor. In particular, tests that fork+exec into system binaries will fail on SELinux-enforcing hosts due to cross-label `/proc//exe` access restrictions. - -Perform this investigation against the current branch and current product behavior. If the issue contains earlier diagnostics, verify them rather than relying on them. - -### A2: Post the Plan Comment - -Post the plan as a comment on the issue. This is the **canonical plan comment** that will be edited in place as the plan evolves. - -```bash -gh issue comment --body "$(cat <<'EOF' -> **🏗️ build-plan** - -## Implementation Plan - -**Issue type:** `` -**Complexity:** -**Confidence:** - -### Summary -<2-3 sentences describing what will be built/changed and the approach> - -### Scope -- ``: -- ``: -- ... - -### Implementation Steps -1. -2. -3. ... - -### Test Plan -- **Unit tests:** -- **Integration tests:** -- **E2E tests:** - -### Risks & Open Questions -- - -### Documentation Impact -- - ---- -*Revision 1 — initial plan* -EOF -)" -``` - -### A3: Mark the Plan Ready in Queue Mode - -If `agent:plan-requested` was present, replace it with `agent:plan-ready`. Do not add `agent:plan-ready` for a direct invocation that was not already using the label workflow. - -```bash -gh issue edit --remove-label "agent:plan-requested" --add-label "agent:plan-ready" -``` - -If the direct request authorized implementation, continue to Branch C. Otherwise report that the plan has been posted and stop. In queue mode, a human reviews the plan and applies `agent:implementation-requested` before an unattended agent can build. - ---- - -## Branch B: Respond to Feedback - -If a plan exists and there are human comments newer than the last agent response, address them. - -### B1: Process Each Unanswered Human Comment - -For each human comment that is newer than the most recent agent comment (plan `updatedAt` or conversation comment `createdAt`): - -1. Read the comment. -2. Quote the relevant portion using `>` blockquote syntax. -3. Formulate a response based on the codebase and the current plan. -4. Post a response with the conversation marker. - -```bash -gh issue comment --body "$(cat <<'EOF' -> **🏗️ build-from-issue-agent** - -> - - -EOF -)" -``` - -### B2: Update the Plan if Needed - -If any feedback requires changes to the plan, **edit the existing plan comment** rather than posting a new one. Use the GitHub API with the comment's node ID: - -```bash -gh api graphql -f query=' - mutation { - updateIssueComment(input: {id: "", body: ""}) { - issueComment { id } - } - } -' -``` - -Or use the REST API: - -```bash -gh api repos/{owner}/{repo}/issues/comments/ -X PATCH -f body="$(cat <<'EOF' -> **🏗️ build-plan** - -## Implementation Plan - -<... updated plan content ...> - ---- -*Revision — * -*Revision — * -*Revision 1 — initial plan* -EOF -)" -``` - -Preserve the full revision history at the bottom so readers can track how the plan evolved. - -Report to the user what feedback was addressed and whether the plan was updated. Stop. - ---- - -## Branch C: Build - -Proceed with implementation when the plan exists and either the user directly requested implementation or `agent:implementation-requested` is present. An existing `agent:in-progress` or `agent:pr-opened` label still triggers the resume or existing-PR checks below. - -### Step 4: Scope Check - -Read the plan comment and check the **Complexity** and **Confidence** fields. - -- **If Complexity is High or Confidence is Low**, warn the user: - - > "This issue is rated High complexity / Low confidence. The plan includes open questions that may need human decisions during implementation. Proceeding, but flagging this for your awareness." - - Continue — do not hard-stop. The user directly requested implementation or chose to apply `agent:implementation-requested`. - -### Step 5: Conflict Detection - -Before creating a branch, check for conflicts: - -#### Check for existing branches - -```bash -git fetch origin -git branch -r | grep -i "" -``` - -If a remote branch referencing this issue ID exists, report it and ask the user whether to continue on that branch or abort. - -#### Check for existing PRs - -```bash -gh pr list --state open --search "Closes #" --json number,title,url -``` - -If an open PR already references this issue, report it and stop. Do not create a competing PR. - -### Step 6: Create Branch - -Determine the branch prefix from the issue type in the plan: - -| Issue type | Branch prefix | -| --- | --- | -| `feat` | `feat/` | -| `fix` | `fix/` | -| `refactor` | `refactor/` | -| `chore` | `chore/` | -| `perf` | `perf/` | -| `docs` | `docs/` | - -Get the current username and create the branch: - -```bash -USERNAME=$(gh api user --jq '.login') -git checkout main -git pull origin main -git checkout -b -/$USERNAME -``` - -### Step 7: Mark Queue Work In Progress - -If `agent:implementation-requested` is present, replace it and `agent:plan-ready` with `agent:in-progress`. In direct mode without a request label, do not add an agent-workflow label. - -```bash -gh issue edit --remove-label "agent:implementation-requested" --remove-label "agent:plan-ready" --add-label "agent:in-progress" -``` - -### Step 8: Implement the Changes - -Follow the implementation steps from the plan. Principles: - -- **Follow the plan**: The plan was reviewed and approved. Stick to it unless you discover something that requires deviation. -- **Minimal scope**: Only change what the plan calls for. No unrelated refactors. -- **If you must deviate**: Note the deviation — it will be included in the PR description. - -Read the relevant source files before making changes. Implement step by step per the plan's sequence. - -### Step 9: Write Tests - -Write tests as specified in the plan's Test Plan section. Follow the project's existing test conventions. - -#### Unit tests - -- Place alongside existing tests for the module (e.g., `#[cfg(test)]` blocks in Rust, `test_*.py` for Python) -- Cover the new/changed behavior, edge cases, and error paths -- Ensure pre-existing behavior still works - -#### Integration tests - -- Place in the project's existing integration test directories -- Cover interactions between the changed components -- Test realistic scenarios including error conditions - -#### E2E tests - -- Only if the plan calls for them -- Cover the full user-facing workflow affected by the change - -#### Test naming - -Use descriptive names that document intent: -- `test_pagination_returns_correct_page_count` -- `test_rejects_negative_offset_parameter` -- `test_retry_succeeds_after_transient_failure` - -### Step 10: Verify — Tests, Lint, Pre-commit (Retry Loop) - -Verification has two phases: unit tests + pre-commit, then E2E tests (if applicable). Run with up to **3 attempts per phase**. - -#### Phase 1: Unit Tests and Pre-commit - -On each attempt: - -```bash -# Run pre-commit checks (linting, formatting, license headers) -mise run pre-commit -``` - -**If verification fails:** - -1. Read the error output carefully. -2. Fix the issues (test failures, lint errors, formatting). -3. Decrement the retry counter and try again. - -**If all 3 attempts fail**, stop and report to the user: -- What passed and what failed -- The specific errors from the last attempt -- That manual intervention is needed - -Do not proceed to Phase 2 or PR creation if Phase 1 is not green. - -#### Phase 2: E2E Tests (Conditional) - -**Trigger**: Run this phase if any files under `e2e/` were added or modified in this build. Check with: - -```bash -git diff --name-only main -- e2e/ -``` - -If there are no changes under `e2e/`, skip this phase entirely. - -If E2E files were modified, run the relevant E2E lane for the driver touched by the change: - -```bash -# Docker-backed gateway smoke E2E -mise run e2e:docker -``` - -Use `mise run e2e:podman`, `mise run e2e:vm`, or a Helm-backed Kubernetes E2E lane when the change targets those drivers. - -**E2E retry loop** (up to 3 attempts): - -1. Run the selected E2E lane. -2. If tests fail: - - Read the pytest output carefully — identify which tests failed and why. - - Distinguish between **test bugs** (the test itself is wrong) and **implementation bugs** (the code under test is wrong). - - Fix the failing code or tests. - - Decrement the retry counter and try again. -3. If tests pass, Phase 2 is green. - -**If all 3 E2E attempts fail**, stop and report to the user: -- Which E2E tests are failing -- The pytest output from the last attempt -- Whether the failures appear to be test issues or implementation issues -- That manual intervention is needed - -Do not proceed to PR creation if E2E verification is not green. - -### Step 11: Update Documentation - -Review the documentation requirements in `AGENTS.md` and update any affected -docs as part of the implementation. Keep documentation changes scoped to the -behavior or subsystem that changed. - -If the implementation changes gateway TOML parsing, `[openshell.gateway]` -fields, `[openshell.drivers.]` fields, driver config defaults, or Helm -rendering of `gateway.toml`, update `docs/how-it-works/gateways/configuration.mdx` in the -same branch. If the change affects user-facing compute-driver setup, also -update `docs/how-it-works/sandboxes/runtimes.mdx` or the relevant deployment -page. - -Use the `sync-agent-infra` skill's maintenance map to identify related skill updates when the implementation changes behavior, commands, or development workflows. Run its full consistency check when the implementation adds, removes, or renames skills or crates; changes workflow relationships or skill coverage; modifies issue or PR templates; or changes agent cross-references. Fix any drift before committing. - -### Step 12: Commit and Push - -Commit all changes using conventional commit format. The `` comes from the issue type in the plan: - -```bash -git add -git commit -m "$(cat <<'EOF' -(): - -Closes # - - -EOF -)" -``` - -Push: - -```bash -git push -u origin HEAD -``` - -### Step 13: Open PR - -Create the PR: - -```bash -gh pr create \ - --title "(): " \ - --body "$(cat <<'EOF' -> **🏗️ build-from-issue-agent** - -## Summary -<1-3 sentences describing what was built and the approach taken> - -## Related Issue -Closes # - -## Changes -- ``: -- ``: - -### Deviations from Plan - - -## Testing -- [x] `mise run pre-commit` passes -- [x] Unit tests added/updated -- [x] E2E tests added/updated (if applicable) - -**Tests added:** -- **Unit:** -- **Integration:** -- **E2E:** - -## Checklist -- [x] Follows Conventional Commits -- [x] Commits are signed off (DCO) - -**Documentation updated:** -- ``: -EOF -)" -``` - -**Display the PR URL** so it's easily clickable: - -``` -Created PR [#](https://github.com/OWNER/REPO/pull/) -``` - -### Step 14: Post-Build Cleanup - -#### Post summary comment on the issue - -```bash -gh issue comment --body "$(cat <<'EOF' -> **🏗️ build-from-issue-agent** - -## Implementation Complete - -PR: [#](https://github.com/OWNER/REPO/pull/) - -### What was built -<1-2 sentence summary> - -### Tests -- Unit: tests added -- Integration: -- E2E: - -### Docs updated -- - -The issue will auto-close when the PR is merged. -EOF -)" -``` - -#### Post E2E attestation comment on the PR - -If E2E tests were run in Phase 2 of Step 10, post an attestation comment on the **PR** documenting that local E2E tests passed. This is necessary because E2E tests are not yet running in CI — this comment serves as the verification record for reviewers. - -Collect the metadata before posting: - -```bash -# Get the commit SHA that was tested -COMMIT_SHA=$(git rev-parse HEAD) - -# Get the test output summary (last few lines of pytest output) -# This was captured during the Phase 2 run — include the pass/fail/skip counts -``` - -Post the attestation: - -```bash -gh pr comment --body "$(cat <<'EOF' -> **🏗️ build-from-issue-agent** - -## E2E Test Attestation - -Local E2E tests passed. CI does not currently run E2E tests, so this comment serves as the verification record. - -| Field | Value | -|-------|-------| -| **Commit** | `` | -| **Command** | `` | -| **Gateway mode** | `` | -| **Result** | ✅ All passed | - -### Test Summary - -``` - -``` - -### Tests Executed -- `::` — PASSED -- `::` — PASSED -- ... -EOF -)" -``` - -Include **every test** that ran (not just the new ones) so the reviewer can see full coverage. If any tests were skipped, note them and explain why. - -#### Update labels - -If `agent:in-progress` is present, replace it with `agent:pr-opened`. Do not add `agent:pr-opened` for an unlabeled direct invocation: - -```bash -gh issue edit --remove-label "agent:in-progress" --add-label "agent:pr-opened" -``` - -#### Report workflow run URL - -Get the workflow run URL from the PR so the user can monitor CI: - -```bash -BRANCH=$(gh pr view --json headRefName --jq '.headRefName') -gh run list --branch "$BRANCH" --limit 1 --json databaseId,status,url -``` - -Report the workflow run URL and suggest the user can use the `watch-github-actions` skill to monitor it. - ---- - -## Branch D: Resume In-Progress Build - -If the `agent:in-progress` label is present, the skill was previously started but may not have completed. - -1. Check for an existing branch matching the issue ID: - ```bash - git branch -r | grep -i "" - ``` -2. If found, check it out and inspect the state (are there uncommitted changes? committed but not pushed? pushed but no PR?). -3. Resume from the appropriate step (9, 10, 12, or 13). -4. If the state is unrecoverable, report to the user and suggest starting fresh. Queue mode requires a human to reapply `agent:implementation-requested`; a new direct implementation request can resume without it. - ---- - -## Useful Commands Reference - -| Command | Description | -| --- | --- | -| `gh issue view --json number,title,body,state,labels,author` | Fetch full issue metadata | -| `gh issue view --json comments` | Fetch all comments on an issue | -| `gh issue comment --body "..."` | Post a comment on an issue | -| `gh api repos/{owner}/{repo}/issues/comments/ -X PATCH -f body="..."` | Edit an existing comment | -| `gh issue edit --add-label "..."` | Add labels | -| `gh issue edit --remove-label "..."` | Remove labels | -| `gh pr list --state open --search "..."` | Search for open PRs | -| `gh pr create --title "..." --body "..."` | Create a pull request | -| `gh api user --jq '.login'` | Get current GitHub username | -| `mise run pre-commit` | Run pre-commit checks (lint, format, license headers) | -| `mise run e2e:docker` | Run smoke E2E against a standalone Docker-backed gateway | -| `mise run e2e:podman` | Run smoke E2E against a Podman-backed gateway | -| `mise run e2e:vm` | Run smoke E2E against the VM compute driver | - -## Example Usage - -### First run — no plan exists - -User says: "Plan issue #42" - -1. Fetch issue #42 — title: "Add pagination to dataset list endpoint" -2. Notice that `state:accepted` and `agent:plan-requested` are absent; warn that the issue does not match the queued workflow, then continue because the user directly requested planning -3. Fetch comments — no `🏗️ build-plan` marker found -4. Pass issue to `principal-engineer-reviewer` for analysis -5. Reviewer produces a plan: feat type, Medium complexity, 3 implementation steps, unit + integration tests needed -6. Post the plan comment with the `🏗️ build-plan` marker -7. Because this direct invocation was unlabeled, leave the `agent:*` workflow labels unchanged -8. Report to user: "Plan posted on issue #42. Awaiting review." - -### Second run — human left feedback - -User says: "Check on issue #42" - -1. Fetch issue #42 and comments -2. Find existing plan comment (Revision 1) -3. Find new human comment: "Should we also paginate the search endpoint?" -4. Post response quoting the question, explaining that search pagination is out of scope for this issue but could be a follow-up -5. Report to user: "Responded to feedback on #42. Plan unchanged." - -### Third run — human revised scope, plan needs update - -User says: "Check issue #42" - -1. Fetch issue #42 and comments -2. Find plan + new human comment: "Actually, let's include search pagination. Updated the issue description." -3. Post response acknowledging the scope change -4. Edit the plan comment to include search endpoint pagination — Revision 2 -5. Report to user: "Updated plan to include search pagination (Revision 2)." - -### Fourth run — implementation requested - -User says: "Build issue #42" - -1. Fetch issue #42 — `state:accepted` is present but `agent:implementation-requested` is absent; warn about the missing queue label and continue because the user directly requested implementation -2. Plan exists (Revision 2), complexity: Medium, confidence: High -3. No conflicting branches or PRs -4. Create branch `feat/42-add-pagination/jmyers` -5. Leave `agent:*` labels unchanged because this direct invocation was not picked up from the queue -6. Implement pagination for both endpoints per the plan -7. Add unit tests for pagination logic, integration tests for both endpoints -8. `mise run pre-commit` passes on first attempt -9. E2E tests skipped (no changes under `e2e/`) -10. Commit, push, create PR with `Closes #42` -11. Post summary comment on issue with PR link -12. No agent-workflow label transition is needed -13. Report PR URL and workflow run status to user - -### Run directly on an issue outside the workflow state machine - -User says: "Build issue #42" - -1. Fetch issue #42 — it has `state:triage-needed`; neither `state:accepted` nor `agent:implementation-requested` is present -2. Warn that triage and acceptance are incomplete and name the missing implementation request label -3. Continue through planning and implementation because the user directly requested the work -4. Do not add, remove, or reinterpret lifecycle or agent-workflow labels - -### Run on issue with existing PR - -User says: "Build issue #42" - -1. Fetch issue #42 — `agent:pr-opened` label present -2. Find existing PR #789 linked to the issue -3. Report: "PR [#789](...) already exists for issue #42. Nothing to build." - -### Run on high-complexity issue - -User says: "Build issue #99" - -1. Fetch issue #99 — warn about any missing expected workflow labels, then continue because the user directly requested implementation -2. Plan exists: complexity High, confidence Low, has open questions -3. Warn user: "Issue #99 is rated High complexity / Low confidence. Proceeding but flagging for your awareness." -4. Continue with build +Do not apply acceptance or roadmap decisions on behalf of a maintainer. Do not introduce `agent:*` workflow labels. diff --git a/.agents/skills/create-github-issue/SKILL.md b/.agents/skills/create-github-issue/SKILL.md index 2510b79272..f47ace151b 100644 --- a/.agents/skills/create-github-issue/SKILL.md +++ b/.agents/skills/create-github-issue/SKILL.md @@ -19,7 +19,7 @@ This project uses YAML form issue templates. When creating issues, match the tem ### Bug Reports -Do not add a type label automatically. The body must include a **User Story**, **Problem Statement**, **Impact / Why This Matters**, and **Acceptance Criteria**, followed by bug-specific reproduction steps and environment details. Logs are optional and must be concise and redacted. Apply area or topic labels only when they are clearly known. +Do not add a type label automatically. Confirm that the human operator personally uses OpenShell and directly encountered the problem or needs the feature for a specific use case. If that first-hand attestation or concrete use case is missing, ask for it before creating the issue. Frame the issue entirely in terms of OpenShell. The body must include a **User Story**, **Problem Statement**, **Impact / Why This Matters**, and **Acceptance Criteria**, followed by bug-specific reproduction steps using only OpenShell deployments and environment details. Do not install third-party tools to demonstrate reproducibility. Logs are optional and must be concise and redacted. If the issue suggests a change to configuration, CLI, SDK, or other user experience, include a notional example of the proposed interaction for human review. Inspect current repository labels before applying any; use only labels whose meaning is clear. ```bash gh issue create \ @@ -27,7 +27,7 @@ gh issue create \ --body "$(cat <<'EOF' ## User Story -As a , I want , so that . +I use OpenShell for . I directly encountered or need so that . ## Problem Statement @@ -52,18 +52,20 @@ As a , I want , so that . - OS: - Runtime, deployment, or integration: +## Suggested UX (if applicable) + + + ## Logs -``` - -``` + EOF )" ``` ### Feature Requests -Do not add a type label automatically. The body must include a **User Story**, **Problem Statement**, **Impact / Why This Matters**, **Proposed Design**, **Acceptance Criteria**, and **Alternatives Considered**. The proposed design should define the user-facing workflow and externally observable behavior without prescribing internal implementation. Agent investigation is optional. Apply area or topic labels only when they are clearly known. +Do not add a type label automatically. Confirm that the human operator personally uses OpenShell and directly encountered the problem or needs the feature for a specific use case. If that first-hand attestation or concrete use case is missing, ask for it before creating the issue. Frame the issue entirely in terms of OpenShell. The body must include a **User Story**, **Problem Statement**, **Impact / Why This Matters**, **Proposed Design**, **Acceptance Criteria**, and **Alternatives Considered**. The proposed design should define the user-facing workflow and externally observable behavior without prescribing internal implementation. Agent investigation is optional. If the issue suggests a change to configuration, CLI, SDK, or other user experience, include a notional example of the proposed interaction for human review. Inspect current repository labels before applying any; use only labels whose meaning is clear. ```bash gh issue create \ @@ -71,7 +73,7 @@ gh issue create \ --body "$(cat <<'EOF' ## User Story -As a , I want , so that . +I use OpenShell for . I directly encountered or need so that . ## Problem Statement @@ -85,13 +87,17 @@ As a , I want , so that . +## Suggested UX (if applicable) + + + ## Acceptance Criteria - [ ] ## Alternatives Considered - + ## Agent Investigation @@ -102,12 +108,16 @@ EOF ### Tasks -For internal tasks that don't fit bug/feature templates: +For internal tasks that do not fit bug/feature templates, still obtain the operator's first-hand OpenShell use case before creating the issue: ```bash gh issue create \ --title ": " \ --body "$(cat <<'EOF' +## User Story + + + ## Description @@ -125,7 +135,7 @@ EOF GitHub built-in issue types (`Bug`, `Feature`, `Task`) should come from the matching issue template when possible, or be set manually afterward. Do not try to emulate them through labels. -Creating an issue does not accept it or queue agent work. Agents never apply `state:accepted`, the `roadmap` label, add issues to the roadmap project, or apply `agent:plan-requested` or `agent:implementation-requested`. Community issues proceed through `triage-issue`; a human accepts technically validated work with `state:accepted` or roadmap placement. The request labels queue work for unattended agents. A user may instead direct an agent to a specific issue; the agent warns about missing expected workflow labels and continues with the requested phase without changing them. +Creating an issue does not accept it. Inspect the repository’s current `state:*` labels and follow its triage → validation → human acceptance process. Agents may assess facts, but only humans decide whether to accept work or place it on the roadmap. A direct user request authorizes the requested planning or implementation phase without changing issue disposition. ## Useful Options @@ -150,5 +160,5 @@ Created issue [#123](https://github.com/OWNER/REPO/issues/123) Use the issue number to: -- Reference in commits: `git commit -m "Fix validation error (fixes #123)"` -- Create a branch following project convention: `-/` +- Reference in signed-off Conventional Commits: `git commit --signoff -m "fix(cli): validate empty requests (fixes #123)"` +- Create a branch following project convention: `/-/`, where `` is a Conventional Commits type. diff --git a/.agents/skills/create-github-pr/SKILL.md b/.agents/skills/create-github-pr/SKILL.md index dd0463df8b..b1c73aa5a2 100644 --- a/.agents/skills/create-github-pr/SKILL.md +++ b/.agents/skills/create-github-pr/SKILL.md @@ -13,7 +13,7 @@ Create pull requests on GitHub using the `gh` CLI. - The `gh` CLI must be authenticated (`gh auth status`) - You must have commits on a branch that's pushed to the remote -- For issue-backed work, the branch should follow `-/`. Exempt issue-less changes may use `/`. +- Every PR must close an existing issue. The branch should follow `/-/`. ## Before Creating a PR @@ -30,13 +30,11 @@ deployment docs. Use the `sync-agent-infra` skill's maintenance map to identify related skill updates when the branch changes behavior, commands, or development workflows. Run its full consistency check when the branch adds, removes, or renames skills or crates; changes workflow relationships or skill coverage; modifies issue or PR templates; or changes agent cross-references. Resolve any drift before creating the PR. -### Run Pre-commit Checks +### Verify the Affected Areas -Run the local pre-commit task before opening a PR: +Use the verification guidance in `CONTRIBUTING.md` to select checks for the changed files and behavior. Guidance, skills, and template changes need applicable Markdown, YAML, link, and consistency checks. Run Rust or SDK suites when those components or their dependencies can be affected. Shared APIs, schemas, dependencies, and build changes may require broader checks even when component source files are unchanged. -```bash -mise run pre-commit -``` +`mise run ci` and `mise run pre-commit` are broad convenience tasks, not blanket PR prerequisites. Broaden validation only for a concrete remaining risk or failed check, and report what actually ran. ### Verify Branch State @@ -49,21 +47,13 @@ Before creating a PR, verify: git branch --show-current ``` -2. **Branch follows naming convention** - Use `-/` for issue-backed work or `/` for an exempt issue-less change. +2. **Branch follows naming convention** - Use `/-/`, where `` is a Conventional Commits type. ```bash - # Example: 1234-add-pagination/jd + # Example: feat/1234-add-pagination/johntmyers git branch --show-current ``` -3. **Consider squashing commits** - For cleaner history, squash related commits before pushing: - - ```bash - # Squash last N commits into one - git reset --soft HEAD~N - git commit -m "feat(component): description" - ``` - ### Push Your Branch Ensure your branch is pushed to the remote: @@ -116,19 +106,25 @@ gh pr create --title "PR title" --body "PR description" ### Link to an Issue -Features, user-visible behavior changes, public API changes, architecture changes, and multi-PR efforts must link an accepted issue. Use `Closes #` in the body to auto-close the issue when merged: +Every PR must close its own issue. Verify that the issue exists, remains open, and covers the PR scope. Use `Closes #` in the body so merge closes it: ```bash gh pr create \ - --title "Fix validation error for empty requests" \ - --body "Closes #123 + --title "fix(cli): validate empty requests" \ + --body "## Summary -## Summary -- Added validation for empty request bodies -- Returns 400 instead of 500" +Validate empty request bodies. + +## Related Issue + +Closes #123 + +## Changes + +- Return 400 instead of 500" ``` -Small documentation fixes, mechanical maintenance, and obvious localized bug fixes may omit a separate issue when the PR contains enough context to review the decision and implementation together. In that case, write `No issue required: ` in the Related Issue section. Do not use this exception for security fixes; follow `SECURITY.md`. +If the work needs multiple PRs, create a separate closable issue for each PR. A higher-level tracking issue may link the component issues, but no PR should close that tracking issue until all its work is complete. Follow `SECURITY.md` for vulnerability disclosure. First-time external contributors must be vouched before their PRs are accepted; the vouch check may close unvouched PRs. Check the current vouch process before opening a PR for an external contributor. ### Create as Draft @@ -138,12 +134,6 @@ For work-in-progress that's not ready for review: gh pr create --draft --title "WIP: New feature" ``` -### With Labels - -```bash -gh pr create --title "Title" --label "area:cli" --label "topic:security" -``` - ### Target a Different Branch Default target is `main`. To target a different branch: @@ -161,15 +151,15 @@ PR descriptions must follow the project's [PR template](.github/PULL_REQUEST_TEM ## Related Issue - + ## Changes ## Testing -- [ ] `mise run pre-commit` passes -- [ ] Unit tests added/updated +- [ ] Checks appropriate to the affected code and behavior pass +- [ ] Unit tests added/updated (if applicable) - [ ] E2E tests added/updated (if applicable) ## Checklist @@ -201,7 +191,7 @@ Closes #456 ## Testing -- [x] `mise run pre-commit` passes +- [x] Relevant CLI format, lint, and unit checks pass - [x] Unit tests added/updated - [ ] E2E tests added/updated (if applicable) diff --git a/.agents/skills/create-spike/SKILL.md b/.agents/skills/create-spike/SKILL.md index 5c6d16c2ec..d5e0878fc4 100644 --- a/.agents/skills/create-spike/SKILL.md +++ b/.agents/skills/create-spike/SKILL.md @@ -1,318 +1,27 @@ --- name: create-spike -description: Investigate a plain-language problem description by deeply exploring the codebase, then create a structured GitHub issue with technical findings. Prequel to build-from-issue — maps vague ideas to concrete, buildable issues. Trigger keywords - spike, investigate, explore, research issue, technical investigation, create spike, new spike, feasibility, codebase exploration. +description: Investigate an OpenShell problem and create a structured issue with technical findings for human disposition. metadata: internal: true --- # Create Spike -Investigate a problem, map it to the codebase, and produce a structured GitHub issue ready for human disposition and roadmap placement. +Investigate a specific OpenShell need and record the findings in a GitHub issue. Use `create-github-issue` for the issue structure and `triage-issue` for the distinction between technical validation and human acceptance. A spike does not authorize implementation or a roadmap decision. -A **spike** is an exploratory investigation. The user has a vague idea — a feature they want, a bug they've noticed, a performance concern — but hasn't mapped it to code, assessed feasibility, or structured it as a buildable issue. This skill does that mapping. +## Before investigating -## Prerequisites +Ask the human operator to attest that they personally use OpenShell and directly encountered the problem or need the feature for a specific use case. If that context is absent, request it before creating the issue. Do not invent a user story or file a generic platform wish as their first-hand need. Search existing issues to avoid duplication. Follow `SECURITY.md` for suspected vulnerabilities instead of filing a public issue. -- The `gh` CLI must be authenticated (`gh auth status`) -- You must be in a git repository with a GitHub remote +## Investigate -## Workflow Overview +1. Reconstruct the current OpenShell workflow and the claimed gap. For a bug, use reproduction steps requiring only OpenShell deployments; do not install third-party tools solely to demonstrate it. +2. Explore the relevant code and tests. Separate observed behavior, likely cause, and open questions. If the claim cannot be validated, state the exact missing evidence. +3. For a feature, describe the desired external behavior and evaluate alternatives, including relevant middleware, interceptors, providers, or other extension points. Prefer an applicable extension when it satisfies the use case; the need to run another service alone does not disqualify it. +4. For configuration, CLI, SDK, or other UX changes, include notional commands, configuration, or API examples for human review. Leave internal implementation choices open unless they are essential constraints. -``` -User describes a problem - │ - ├─ Step 1: Gather the problem statement - │ └─ Ask ONE round of clarifying questions if genuinely needed - │ - ├─ Step 2: Deep codebase investigation via principal-engineer-reviewer - │ └─ Map the problem to code, assess feasibility, identify risks - │ - ├─ Step 3: Determine labels from the repo - │ - ├─ Step 4: Create a GitHub issue with structured findings - │ - └─ Step 5: Report to user with issue URL and next steps -``` +## Record the result -## Step 1: Gather the Problem Statement +Create an issue with User Story, Problem Statement, Impact / Why This Matters, Proposed Design when relevant, Acceptance Criteria, Alternatives Considered, and concise Agent Investigation. Include OpenShell-only reproduction and environment details for bugs. Inspect current GitHub `state:*` labels and descriptions before applying the one that matches the evidence; do not hard-code label names. Do not apply an acceptance state or add the issue to the roadmap. -The user provides a problem description. This could be: - -- A feature idea: "I want sandboxes to be able to reach private IPs" -- A bug report: "The retry logic in the proxy seems too aggressive" -- A performance concern: "Policy evaluation is slow for large rule sets" -- A refactoring goal: "The config parsing is scattered across too many modules" - -Extract from the user's input: - -1. **What** they want (the desired outcome or observed problem) -2. **Why** they want it (motivation, use case, or trigger) -3. **Constraints** they've mentioned (backwards compatibility, performance targets, etc.) - -### Clarification policy - -If the problem is too vague to determine which area of the codebase to investigate, ask **ONE** round of clarifying questions. Do not over-interrogate. Examples of when to ask: - -- "Make things faster" — ask which component or operation is slow -- "Fix the networking" — ask what specific behavior is wrong - -Examples of when NOT to ask: - -- "The retry logic in the proxy is too aggressive" — clear enough, start investigating -- "Allow sandbox egress to private IP space" — clear enough, start investigating -- "The OPA policy evaluation needs caching" — clear enough, start investigating - -## Step 2: Deep Codebase Investigation - -This is the core of the skill. Use the Task tool with the `principal-engineer-reviewer` sub-agent to perform a thorough codebase investigation. - -``` -Task tool with subagent_type="principal-engineer-reviewer" -``` - -The prompt to the reviewer **must** instruct it to: - -1. **Identify which components/subsystems are involved.** Don't just guess from names — read the code to confirm. - -2. **Read the relevant source files thoroughly.** Not just grep for keywords — actually read and understand the logic. Follow the call chain from entry point through to the relevant behavior. - -3. **Map the current architecture for the affected area.** How do the components interact? What's the data flow? Where are the boundaries? - -4. **Identify the exact code paths that would need to change.** Provide file paths and line numbers. Name the functions, structs, and modules. - -5. **Assess feasibility and complexity:** - - **Low**: Isolated change, < 3 files, clear path forward - - **Medium**: Multiple files/components, some design decisions, but well-scoped - - **High**: Cross-cutting changes, architectural decisions needed, significant unknowns - -6. **Identify risks, edge cases, and design decisions that need human input.** What could go wrong? What trade-offs exist? What decisions shouldn't be made by an agent? - -7. **Check for existing patterns in the codebase that should be followed.** If there's a convention for how similar features are implemented, note it. The implementation should be consistent. - -8. **Look at relevant tests to understand test coverage expectations.** What test patterns exist? What level of coverage is expected for this area? - -9. **Check design records** in `rfc/` and the affected crate `README.md` files for relevant decisions and constraints. - -10. **Assess gateway config documentation impact.** If the change would add, remove, rename, or change defaults for gateway TOML keys or driver-specific config options, call out that `docs/how-it-works/gateways/configuration.mdx` must be updated. If the change is surfaced through Helm or compute-driver setup docs, call out the relevant deployment or compute-driver docs too. - -11. **Assess Linux Security Module (LSM) impact.** If the change involves process identity, `/proc` filesystem access, file labeling, binary execution, or inter-process visibility, call out whether it will behave differently on hosts running SELinux (enforcing) or AppArmor. For example: reading `/proc//exe` across an SELinux domain boundary returns ENOENT, not EACCES. Tests that fork+exec into system binaries (different SELinux label) will fail on enforcing hosts. Flag any LSM-sensitive code paths and recommend mitigations. - -12. **Determine the issue type:** `feat`, `fix`, `refactor`, `chore`, `perf`, or `docs`. - -### What makes a good investigation prompt - -Include in the prompt to the reviewer: - -- The user's problem statement (verbatim or lightly paraphrased) -- Any constraints the user mentioned -- A clear instruction to return: component list, file references with line numbers, architecture summary, feasibility assessment, risks, and the issue type - -### What to do with the results - -The reviewer will return a detailed analysis. You'll use this to populate the issue body (Step 4). The issue should contain both the stakeholder-readable summary and the full technical investigation — everything in one place. - -## Step 3: Determine Labels - -Fetch the available labels from the repository: - -```bash -gh label list --limit 100 -``` - -Based on the investigation results, select appropriate labels: - -- **Do not add issue type labels** — GitHub built-in issue types come from issue templates or manual follow-up, not labels -- **Include area labels** if they exist in the repo (e.g., `area:sandbox`, `area:proxy`, `area:policy`, `area:cli`) -- **Do not invent labels** — only use labels that already exist in the repo -- **Add `state:validated` only when the evidence is sufficient for human disposition** — the spike established a coherent problem or proposal and completed the factual assessment needed for a human yes/no decision -- **Add `state:needs-info` instead when material evidence is missing** — identify the exact evidence, reproduction details, or decision input still needed in the issue body -- **Never add `state:accepted`, an `agent:*` label, or the `roadmap` label** — acceptance, roadmap placement, and requests for agent work require a human decision - -## Step 4: Create the GitHub Issue - -Create the issue with a structured body containing both the stakeholder-readable summary and the full technical investigation. The title should follow conventional commit format. - -```bash -gh issue create \ - --title ": " \ - --label "" --label "" \ - --body "$(cat <<'EOF' -## Problem Statement - - - -## Technical Context - - - -## Affected Components - -| Component | Key Files | Role | -|-----------|-----------|------| -| | ``, `` | | -| ... | ... | ... | - -## Technical Investigation - -### Architecture Overview - - - -### Code References - -| Location | Description | -|----------|-------------| -| `:` | | -| `:` | | -| ... | ... | - -### Current Behavior - - - -### What Would Need to Change - - - -### Alternative Approaches Considered - - - -### Patterns to Follow - - - -## Proposed Approach - - - -## Scope Assessment - -- **Complexity:** -- **Confidence:** -- **Estimated files to change:** -- **Issue type:** `` - -## Risks & Open Questions - -- -- -- ... - -## Disposition Readiness - -- **State:** `` -- **Assessment:** -- **Missing evidence:** - -## Test Considerations - -- -- -- -- - ---- -*Created by spike investigation. `state:validated` means the issue is ready for human disposition; `state:needs-info` means specific evidence is still required. A human applies `state:accepted` or places the issue on the roadmap if OpenShell should pursue the work. To queue unattended agent planning, a human applies `agent:plan-requested`; on a direct request, the agent warns about missing expected workflow labels and continues without changing them.* -EOF -)" -``` - -**Do NOT post a follow-up comment on the issue.** All findings must be contained in the issue body itself. - -**Display the issue URL** so it's easily clickable: - -``` -Created issue [#](https://github.com/OWNER/REPO/issues/) -``` - -## Step 5: Report to User - -After creating the issue, report: - -1. The issue URL (as a clickable markdown link) -2. A 2-3 sentence summary of what was found -3. Key risks or decisions that need human attention -4. Next steps: - -For `state:validated`: - -> Review the issue and decide whether OpenShell should pursue it. If yes, apply `state:accepted`, associate it with a roadmap item, or do both. Either action records acceptance; roadmap placement additionally records sequencing. The work may remain human-owned. Apply `agent:plan-requested` to queue planning for an unattended agent, or directly ask an agent to use `build-from-issue`; on a direct request, the agent warns about missing expected workflow labels and continues without changing them. If no, close it as not planned and record the rationale. - -For `state:needs-info`: - -> Collect the missing evidence identified in the issue. Leave it off the roadmap. Once the evidence is sufficient, replace `state:needs-info` with `state:validated` for human disposition. - -## Design Principles - -1. **Everything goes in the issue body.** Do NOT post follow-up comments. The issue body should contain both the stakeholder-readable summary and the full technical investigation, all in one place. - -2. **Do NOT create an implementation plan.** The spike identifies the problem space and proposes a direction. The implementation plan is `build-from-issue`'s responsibility, created after human review of the spike. - -3. **One round of clarification max.** Don't turn this into an interrogation. If the user provides enough to identify the area of the codebase, start investigating. - -4. **The issue should save `build-from-issue` work.** When `build-from-issue` runs, it reads the issue body as input context. The technical investigation section should contain enough detail that its `principal-engineer-reviewer` can build on the investigation rather than starting from scratch. - -5. **Cross-reference `build-from-issue`.** Mention it as the natural next step in the issue body footer. - -6. **Treat validation as an evidence threshold, not an automatic spike outcome.** Apply `state:validated` only when the investigation supports a human accept/decline decision. Otherwise apply `state:needs-info`, state what is missing, and leave the issue off the roadmap. - -## Useful Commands Reference - -| Command | Description | -| --- | --- | -| `gh issue create --title "..." --body "..." --label "..."` | Create a new issue | -| `gh label list --limit 100` | List available labels in the repo | -| `gh issue edit --add-label "..."` | Add labels to an issue | -| `gh issue view --json number,title,body,state,labels` | Fetch issue metadata | - -## Example Usage - -### Feature spike - -User says: "Allow sandbox egress to private IP space via networking policy" - -1. Problem is clear — no clarification needed -2. Fire `principal-engineer-reviewer` to investigate: - - Finds `is_internal_ip()` SSRF check in `proxy.rs` that blocks RFC 1918 addresses - - Reads OPA policy evaluation pipeline in `opa.rs` and `crates/openshell-sandbox/data/sandbox-policy.rego` - - Reads proto definitions in `sandbox.proto` for `NetworkEndpoint` - - Maps the 4-layer defense model: netns, seccomp, OPA, SSRF check - - Reads RFC 0002 and the `openshell-policy` crate README - - Identifies exact insertion points: policy field addition, SSRF check bypass path, OPA rule extension - - Assesses: Medium complexity, High confidence, ~6 files -3. Fetch labels — select `area:sandbox`, `area:proxy`, `area:policy`, `state:validated` -4. Create issue: `feat: allow sandbox egress to private IP space via networking policy` — body includes both the summary and full investigation (code references, architecture context, alternative approaches) -5. Report: "Created issue #59. The investigation found that private IP blocking is enforced at the SSRF check layer in the proxy. The proposed approach adds a policy-level override. A human must now accept or decline it and place it on the roadmap if accepted." - -### Bug investigation spike - -User says: "The proxy retry logic seems too aggressive — I'm seeing cascading failures under load" - -1. Problem is clear enough — investigate retry behavior in the proxy -2. Fire `principal-engineer-reviewer`: - - Finds retry configuration in proxy request handling - - Reads the retry loop, backoff strategy, and timeout settings - - Checks if there's circuit breaker logic - - Maps the failure propagation path - - Identifies that retries happen without backoff jitter, causing thundering herd - - Assesses: Low complexity, High confidence, ~2 files -3. Fetch labels — select `area:proxy`, `state:validated` -4. Create issue: `fix: proxy retry logic causes cascading failures under load` — body includes both the summary and full investigation (retry code references, current behavior trace, comparison to standard backoff patterns) -5. Report: "Created issue #74. The proxy retries without jitter or circuit breaking, which amplifies failures under load. A human must now accept or decline it and place it on the roadmap if accepted." - -### Performance/refactoring spike - -User says: "Policy evaluation is getting slow — can we cache compiled OPA policies?" - -1. Problem is clear — investigate OPA policy evaluation performance -2. Fire `principal-engineer-reviewer`: - - Reads the OPA evaluation pipeline end to end - - Measures where policies are loaded and compiled (per-request vs. cached) - - Checks if there's an existing caching layer - - Reads the policy reload/hot-swap mechanism - - Identifies that policies are recompiled on every evaluation - - Assesses: Medium complexity, Medium confidence (cache invalidation is a design decision), ~4 files -3. Fetch labels — select `area:policy`, `state:validated` -4. Create issue: `perf: cache compiled OPA policies to reduce evaluation latency` — body includes both the summary and full investigation (compilation hot path, per-request overhead, cache invalidation strategies with trade-offs) -5. Report: "Created issue #81. Policies are recompiled per-request with no caching. The main design decision is the cache invalidation strategy. A human must now accept or decline it and place it on the roadmap if accepted." +Report the issue URL, technical findings, uncertainties, and the human disposition needed. For subsequent authorized implementation, use `build-from-issue`. Every eventual PR must close an issue covering its own scope; split multi-PR efforts into separate closable issues and use a high-level issue only for tracking. diff --git a/.agents/skills/fix-security-issue/SKILL.md b/.agents/skills/fix-security-issue/SKILL.md index 1452cbe66c..30e6fa15bd 100644 --- a/.agents/skills/fix-security-issue/SKILL.md +++ b/.agents/skills/fix-security-issue/SKILL.md @@ -1,322 +1,18 @@ --- name: fix-security-issue -description: Implement a fix for a reviewed security issue. Takes a directly requested issue number or scans for issues labeled `topic:security` and `agent:implementation-requested`. Reads the security review from the issue comments and implements the remediation plan. Trigger keywords - fix security issue, remediate security, implement security fix, patch vulnerability. +description: Implement an authorized fix for a reviewed security issue and open a PR that closes its issue. metadata: internal: true --- # Fix Security Issue -Implement a code fix for a security issue that has already been reviewed by the `review-security-issue` skill. +Use this skill after an authorized `review-security-issue` review identifies an actionable concern. Follow `SECURITY.md`; do not disclose vulnerability details in a public issue. A direct user request to fix a specific reviewed issue authorizes implementation. For unattended work, inspect current `state:*` label descriptions, maintainer assignments, and comments to verify that remediation is authorized. Do not infer approval from a state that only records technical validation. -## Prerequisites +1. Fetch the issue and its comments with `gh issue view --json number,title,body,state,labels,comments`. Inspect current repository labels and confirm this is a security issue. Find the review marked `> **🔒 security-review-agent**` and its remediation plan. If the review is missing or found the issue not actionable, stop and report that result. +2. Verify the review against current code. Adapt the plan when code has changed, and record material deviations. Check for an existing owner, branch, or PR. +3. Create a branch or worktree using `fix/-/`, preserving unrelated changes. Implement the smallest safe fix and add regression tests for the security boundary. Avoid logging secrets or adding public exploit detail. +4. Follow the verification guidance in `CONTRIBUTING.md`. Run format, lint, compile or type checks, and regression tests for the affected security boundary and dependent components, plus the relevant E2E lane for sandbox or policy changes. Broaden verification when the fix spans components or a concrete risk remains; do not require unaffected Rust or SDK suites solely to create a signed-off commit or PR. +5. Follow `create-github-pr` and use `Closes #` for the reviewed issue. Every PR must close its own issue; split multi-PR remediations into separate issues in the authorized security workflow. Keep the PR description appropriately scoped to its disclosure venue. -- The `gh` CLI must be authenticated (`gh auth status`) -- You must be in a git repository with a GitHub remote -- The issue must have `topic:security`. In unattended scan mode it must also have `agent:implementation-requested`; for a direct user request, warn if that workflow label is missing and continue without changing it. -- The issue must have a prior security review comment (posted by `review-security-issue`) with a **Legitimate concern** determination and a remediation plan - -## Agent Comment Marker - -All PR descriptions and comments posted by this skill **must** begin with the following marker line: - -``` -> **🔧 security-fix-agent** -``` - -This distinguishes fix-agent content from review-agent comments (`🔒 security-review-agent`) and human comments. - -## Step 1: Identify the Issue - -The user may provide an issue number directly, or ask the agent to find issues to fix. - -### If an issue number is provided - -Strip any leading `#` and proceed to Step 2 with that issue ID. The user's explicit fix request authorizes implementation. If `agent:implementation-requested` is absent, warn that the expected workflow label is missing and continue without changing it. - -### If no issue number is provided - -Scan for open issues labeled `topic:security` and `agent:implementation-requested`: - -```bash -gh issue list --label "topic:security" --label "agent:implementation-requested" --state open --json number,title,labels,updatedAt -``` - -- **If no issues are found**, report to the user that there are no security issues ready for fixing and stop. -- **If one issue is found**, proceed to Step 2 with that issue. -- **If multiple issues are found**, list them for the user and ask which one to work on. If the user said to handle all of them, process them sequentially (one full fix cycle per issue). - -## Step 2: Fetch the Issue and Validate Labels - -Fetch the issue details: - -```bash -gh issue view --json number,title,body,state,labels,author -``` - -### Validate the Security Label and Invocation Mode - -Check the issue's `labels` array from the response above: - -- `topic:security` is required because this specialized skill handles security issues. -- `agent:implementation-requested` is required only when an unattended agent discovered the issue by scanning the queue. - -If `topic:security` is missing, report that this skill only handles security issues and stop. If queue mode selected an issue without `agent:implementation-requested`, report that it is not ready for unattended pickup and stop. - -Never apply `agent:implementation-requested` yourself. In direct mode, warn about its absence and continue; the missing label does not block the user's request to fix the specific issue. - -### Validate the security review - -Once labels are confirmed, fetch the comments to find the security review: - -```bash -gh issue view --json comments --jq '[.comments[] | select(.body | contains("security-review-agent"))]' -``` - -- **If no `security-review-agent` comment is found**, report to the user that this issue has not been reviewed yet. Suggest running the `review-security-issue` skill first. Stop. -- **If the review determination is "Not actionable"**, report to the user that the review found no actionable concern. There is nothing to fix. Stop. -- **If the review determination is "Legitimate concern"**, extract the **Remediation Plan** and **Severity Assessment** sections from the review comment. Proceed to Step 3. - -## Step 3: Plan the Implementation - -Before writing code, analyze the remediation plan from the review comment: - -1. Identify all files and components mentioned in the remediation plan. -2. Read those files to understand the current code. -3. Determine if the remediation plan is still accurate given the current state of the code (the codebase may have changed since the review). -4. Break the fix into discrete, testable changes. - -If the remediation plan references files or components that no longer exist or have changed significantly, adapt the plan accordingly and note the deviations. - -## Step 4: Create a Branch - -Create a working branch for the fix: - -```bash -git checkout -b fix/security-- -``` - -Follow the project's branch naming conventions. The branch name should reference the issue ID. - -In queue mode, replace the human request and ready-plan labels with the agent execution state. For an unlabeled direct invocation, do not add an agent-workflow label: - -```bash -gh issue edit --remove-label "agent:implementation-requested" --remove-label "agent:plan-ready" --add-label "agent:in-progress" -``` - -## Step 5: Implement the Fix - -Implement the changes described in the remediation plan. Follow these principles: - -- **Minimal scope**: Only change what is necessary to address the security concern. Avoid unrelated refactors. -- **Defense in depth**: Where appropriate, add multiple layers of protection (input validation, output encoding, access checks, etc.). -- **No regressions**: Ensure existing tests still pass after the fix. - -After implementing, run the project's pre-commit checks: - -```bash -mise run pre-commit -``` - -Fix any issues that arise before proceeding. - -## Step 6: Write Tests - -Every security fix **must** include tests that verify the vulnerability is resolved. Choose the appropriate test level(s) based on the nature of the fix: - -### Unit tests - -Add unit tests when the fix changes a specific function, method, or module in isolation. Place them alongside the existing tests for that module (e.g., same `tests/` directory or `#[cfg(test)]` block for Rust, `test_*.py` for Python). - -Unit tests should cover: -- The previously-vulnerable code path now rejects malicious input or behaves correctly -- Edge cases around the security boundary (empty input, oversized input, special characters, etc.) -- That legitimate inputs continue to work as before - -### Integration / E2E tests - -Add integration or end-to-end tests when the vulnerability spans multiple components or is triggered via an API endpoint, CLI command, or network boundary. Place them in the project's existing integration or e2e test directories. - -Integration tests should cover: -- The full attack scenario described in the security review is no longer exploitable -- The fix holds under realistic conditions (authenticated vs. unauthenticated, different roles, etc.) - -### Test naming - -Name tests descriptively to document the security concern: -- `test_rejects_sql_injection_in_search_query` -- `test_blocks_path_traversal_in_file_upload` -- `test_enforces_auth_on_admin_endpoint` - -### Verify - -Run the full relevant test suite to confirm both the new tests pass and no existing tests regress: - -```bash -# Run tests relevant to the changed components -# The specific command depends on the project area affected -``` - -If the review identified a specific exploit scenario, verify that it is no longer possible with the fix in place. - -## Step 7: Update Documentation - -Review the documentation requirements in `AGENTS.md` and update any affected -docs as part of the security fix. If the fix is purely internal, such as -switching to parameterized queries with no external behavior change, -documentation updates may not be needed. - -## Step 8: Commit, Push, and Open PR - -### Commit - -Commit all changes (implementation, tests, and documentation) using conventional commit format: - -```bash -git add -git commit -m "$(cat <<'EOF' -fix(security): - -Closes # - - -EOF -)" -``` - -### Push - -```bash -git push -u origin HEAD -``` - -### Create the PR - -Create a PR that closes the security issue. Put the full fix summary in the PR description rather than commenting on the issue -- the `Closes #` directive will auto-close the issue when merged. - -```bash -gh pr create \ - --title "fix(security): " \ - --label "topic:security" \ - --body "$(cat <<'EOF' -> **🔧 security-fix-agent** - -Closes # - -## Security Fix - -### Summary -<1-3 sentences describing the security issue and how it was fixed> - -### Severity Assessment -- **Impact:** -- **Exploitability:** -- **Affected components:** - -### Changes Made -- ``: -- ``: - -### Tests Added -- **Unit:** -- **Integration/E2E:** - -### Documentation Updated -- ``: - -### Verification - -EOF -)" -``` - -**Display the PR URL** so it's easily clickable: - -``` -Created PR [#](https://github.com/OWNER/REPO/pull/) -``` - -In queue mode, replace `agent:in-progress` with `agent:pr-opened` after the PR is created. For an unlabeled direct invocation, do not add an agent-workflow label: - -```bash -gh issue edit --remove-label "agent:in-progress" --add-label "agent:pr-opened" -``` - -## Step 9: Report to User - -Summarize what was done: - -1. Which issue was addressed and link to it -2. What the vulnerability was -3. What changes were made (files, approach) -4. What tests were added and at which level (unit, integration, e2e) -5. What documentation was updated -6. Link to the PR - -## Useful Commands Reference - -| Command | Description | -| --- | --- | -| `gh issue list --label "topic:security" --label "agent:implementation-requested" --state open` | Find security issues whose fixes a human requested | -| `gh issue view --json number,title,body,state,labels,author` | Fetch full issue metadata | -| `gh issue view --json comments` | Fetch all comments on an issue | -| `gh pr create --title "..." --body "..."` | Create a pull request | -| `gh api user --jq '.login'` | Get current GitHub username | -| `gh issue view ` | View issue details | -| `mise run pre-commit` | Run pre-commit checks | - -## Example Usage - -### Fix a specific issue - -User says: "Fix security issue #42" - -1. Fetch issue #42 and its comments -2. Find the `security-review-agent` review with determination "Legitimate concern" -3. Extract the remediation plan (e.g., add input sanitization to API handler) -4. Create branch `fix/security-42-input-sanitization` -5. Implement the fix -6. Add unit tests for the sanitization function and an integration test for the endpoint -7. Update affected documentation per `AGENTS.md`, if needed -8. Commit, push, and open PR with `Closes #42` -9. Report the PR link and changes to the user - -### Scan and fix requested security issues - -User says: "Fix any ready security issues" - -1. Query for open issues with labels `topic:security` + `agent:implementation-requested` -2. Find issue #78: "SQL injection in search endpoint" -3. Fetch the review comment -- determination is "Legitimate concern" -4. Implement parameterized queries -5. Add `test_rejects_sql_injection_in_search_query` unit test and e2e test for the search endpoint -6. Update affected documentation per `AGENTS.md`, if needed -7. Commit, push, open PR with `Closes #78`, report to user - -### Issue with non-actionable review - -User says: "Fix security issue #99" - -1. Fetch issue #99 and its comments -2. Find the `security-review-agent` review with determination "Not actionable" -3. Report to the user: "Issue #99 was reviewed and determined to be not actionable. No fix is needed." -4. Stop - -### Directly requested issue without `agent:implementation-requested` - -User says: "Fix security issue #55" - -1. Fetch issue #55 metadata -2. Labels are `["topic:security"]` -- missing `agent:implementation-requested` -3. Confirm that a legitimate security review and remediation plan exist -4. Warn that `agent:implementation-requested` is missing from the expected workflow state -5. Proceed because the user's direct request authorizes implementation; leave the labels unchanged - -### Issue without a review - -User says: "Fix security issue #60" - -1. Fetch issue #60 metadata -- `topic:security` is present and the user directly requested the fix -2. Fetch comments -- no `security-review-agent` comment found -3. Report to the user: "Issue #60 has not been reviewed yet. Run the review-security-issue skill first." -4. Stop +Begin any fix comments with `> **🔧 security-fix-agent**`. Do not change human disposition or introduce `agent:*` workflow labels. diff --git a/.agents/skills/review-security-issue/SKILL.md b/.agents/skills/review-security-issue/SKILL.md index efb054df80..0a014a62b5 100644 --- a/.agents/skills/review-security-issue/SKILL.md +++ b/.agents/skills/review-security-issue/SKILL.md @@ -1,196 +1,19 @@ --- name: review-security-issue -description: Given a GitHub issue, review the issue for security implications. You'll make a determination if the claim in the issue is legitimate and should be addressed or will be a "won't fix." Trigger keywords - security issue, review security ticket, review security issue. +description: Review an authorized security issue for validity, severity, and a remediation plan. metadata: internal: true --- # Review Security Issue -Review an issue that outlines a security, vulnerability, or privacy concern. +Review a security concern through its authorized private workflow. Do not file or expand a vulnerability in a public issue; follow `SECURITY.md`. A direct request to review authorizes review only, not remediation. For unattended review, inspect current `state:*` label descriptions, maintainer assignments, and comments to verify that review is authorized. -## Prerequisites +## Assess -- The `gh` CLI must be authenticated (`gh auth status`) -- You must be in a git repository with a GitHub remote -- The issue must have `topic:security`. In unattended queue mode it must also have `agent:plan-requested`; for a direct user request, warn if that workflow label is missing and continue without changing it. +1. Fetch the issue and comments with `gh issue view --json title,body,state,labels,comments`. Inspect current repository labels rather than assuming exact names. Verify that this is an authorized security issue and that a prior review does not already answer the request. +2. Inspect affected code and verify the claim. Assess impact, exploitability, prerequisites, affected surface, and a concrete attack scenario. Separate evidence from assumptions and give a severity with rationale. +3. If actionable, propose a remediation plan with code areas, safe rollout, and focused tests. If not actionable, explain the evidence and recommended disposition. Do not decide product acceptance or silently close the issue. +4. Post the review only when the request authorizes posting. Begin the comment with `> **🔒 security-review-agent**` so later reviews can detect it. Keep sensitive details in the authorized private venue. -## Agent Comment Marker - -All comments posted by this skill **must** begin with the following marker line so that prior reviews can be detected and human comments can be distinguished from agent comments: - -``` -> **🔒 security-review-agent** -``` - -This marker is used in Step 2 to detect prior reviews and in Step 5 to distinguish agent comments from human comments. - -## Step 1: Fetch the Issue - -The user will provide an issue ID (e.g., `#42` or `42`). Strip any leading `#` and fetch the issue contents. - -```bash -gh issue view -``` - -To also retrieve the full issue body as JSON (useful for parsing): - -```bash -gh issue view --json title,body,state,labels,author -``` - -## Step 2: Check if Review is Needed - -First, check the issue's labels from the metadata fetched in Step 1. - -- **If the issue has `agent:implementation-requested`**, the issue has already been reviewed and a human authorized remediation. There is no review to perform. Suggest using `fix-security-issue` and stop. -- **If `topic:security` is missing**, report that this specialized skill only reviews security issues and stop. -- **If this is queue mode and `agent:plan-requested` is missing**, report that the issue is not ready for unattended pickup and stop. -- **If the user directly requested review of this issue**, warn that `agent:plan-requested` is missing, then proceed without it. Never add or offer to add the human-only request label. - -Next, fetch existing comments on the issue: - -```bash -gh issue view --json comments --jq '.comments[].body' -``` - -Search the comments for the agent marker (`> **🔒 security-review-agent**`). - -- **If the marker is found** and no subsequent human comments exist that ask follow-up questions or challenge the review, you are done. Report to the user that a review already exists. -- **If the marker is found** but there are newer human comments with questions or objections, proceed to Step 5 to address them. -- **If the marker is not found**, proceed to Step 3. - -## Step 3: Analyze the Issue - -Pass the issue title, description, and any relevant code references to the `principal-engineer-reviewer` sub-agent for analysis. Use the Task tool: - -``` -Task tool with subagent_type="principal-engineer-reviewer" -``` - -In the prompt, instruct the reviewer to approach the issue with a security-focused lens, specifically evaluating: - -- **Validity**: Is this a real security, vulnerability, or privacy concern? -- **Severity**: What is the potential impact (data exposure, privilege escalation, denial of service, etc.)? -- **Exploitability**: How easy is it to exploit? Does it require authentication, specific conditions, or access? -- **Attack scenario**: What are the concrete steps an attacker would take to exploit this, from their perspective? -- **Affected surface**: Which components, endpoints, or code paths are affected? -- **Recommendation**: Should this be fixed, mitigated, accepted as risk, or closed as not actionable? - -## Step 4: Post the Review - -Based on the analysis from Step 3, post a comment on the issue. - -### If the concern is legitimate - -Post a comment with a remediation plan: - -```bash -gh issue comment --body "$(cat <<'EOF' -> **🔒 security-review-agent** - -## Security Review - -**Determination:** Legitimate concern - -### Summary -<1-3 sentences describing the security issue and its impact> - -### Severity Assessment -- **Impact:** -- **Exploitability:** -- **Affected components:** - -### Attack Scenario -Step-by-step from the attacker's perspective: -1. -2. -3. - -### Remediation Plan -1. -2. -3. ... - -### Additional Notes - -EOF -)" -``` - -### If the concern is not actionable - -Post a comment with a rationale: - -```bash -gh issue comment --body "$(cat <<'EOF' -> **🔒 security-review-agent** - -## Security Review - -**Determination:** Not actionable - -### Rationale - - -### References - -EOF -)" -``` - -## Step 5: Mark the Security Plan Ready - -After posting a legitimate-concern review with a remediation plan, replace `agent:plan-requested` with `agent:plan-ready` only when the request label was present: - -```bash -gh issue edit --remove-label "agent:plan-requested" --add-label "agent:plan-ready" -``` - -This signals that an unattended agent produced a remediation plan that awaits human review. For an unlabeled direct invocation, leave the `agent:*` labels unchanged. A later direct request can authorize remediation without `agent:implementation-requested`; warn that the expected label is missing and continue, while unattended remediation still requires that label. For a not-actionable determination, remove `agent:plan-requested` if present, do not add another `agent:*` label, and report that a human should close the issue or record the risk decision. - -## Step 6: Address Follow-up Comments - -After posting (or if a prior review exists with new human comments), review all comments that do **not** contain the `> **🔒 security-review-agent**` marker. These are human comments. - -For each unanswered human comment: - -1. Read the question or objection. -2. Formulate a response based on the codebase and the prior security analysis. -3. Post a reply that begins with the agent marker. - -**Important:** The authenticated user posting these comments may be a real person's account. Humans may reply to your comments directly. Always use the agent marker to distinguish your comments from theirs. - -## Useful Commands Reference - -| Command | Description | -| --- | --- | -| `gh issue view ` | View issue details | -| `gh issue view --json title,body,state,labels,author` | Fetch full issue metadata as JSON | -| `gh issue view --json comments --jq '.comments[].body'` | Fetch all comments on an issue | -| `gh issue comment --body "..."` | Post a comment on an issue | -| `gh issue edit --remove-label "agent:plan-requested" --add-label "agent:plan-ready"` | Mark a remediation plan ready for human review | - -## Example Usage - -### Review a security issue - -User says: "Review security issue #42" - -1. Fetch issue #42 via `gh issue view 42` -2. Fetch comments and check for the `security-review-agent` marker -3. No prior review found -- pass issue to `principal-engineer-reviewer` with security lens -4. Reviewer determines it's a legitimate XSS vulnerability in the API response handler -5. Post a comment with severity assessment and remediation plan -6. If `agent:plan-requested` was present, replace it with `agent:plan-ready`; otherwise leave the direct invocation unlabeled -7. Report the finding and posted comment to the user - -### Re-review with new comments - -User says: "Check on security issue #42 again" - -1. Fetch issue #42 and its comments -2. Find existing `security-review-agent` review from a prior run -3. Detect two new human comments asking about scope of the vulnerability -4. Post responses to each, prefixed with the agent marker -5. Report to the user what was addressed +A human decides whether to authorize remediation. Route an authorized fix to `fix-security-issue`. Do not introduce `agent:*` workflow labels. diff --git a/.agents/skills/sync-agent-infra/SKILL.md b/.agents/skills/sync-agent-infra/SKILL.md index 68a623c392..f16c171bea 100644 --- a/.agents/skills/sync-agent-infra/SKILL.md +++ b/.agents/skills/sync-agent-infra/SKILL.md @@ -1,214 +1,48 @@ --- name: sync-agent-infra -description: Detect and fix drift across agent-first infrastructure files. Ensures skill inventories, workflow chains, architecture tables, issue/PR templates, and cross-references stay consistent when skills, crates, or workflows change. Run after adding, removing, or renaming skills or components. Trigger keywords - sync agent infra, sync skills, update agent docs, check agent consistency, agent infra drift, sync contributing, sync agents. +description: Reconcile contributor skills, AGENTS.md, CONTRIBUTING.md, issue and PR templates, and workflow references after repository workflow changes. metadata: internal: true --- # Sync Agent Infrastructure -Detect and fix drift across the agent-first infrastructure files. These files reference each other and must stay consistent: +Keep contributor guidance consistent without copying procedural workflows into `AGENTS.md`. That file holds repository coding conventions and pointers; the relevant skills hold issue, PR, and maintenance procedures. `CONTRIBUTING.md` explains the human workflow. -| File | What it tracks | -|------|---------------| -| `AGENTS.md` | Project identity, workflow chains, architecture overview, issue/PR conventions, skill maintenance pointer | -| `CONTRIBUTING.md` | Skills table, workflow chains, "When to Open an Issue" guidance, skill references | -| `CONTRIBUTING.md` issue lifecycle section | Human-facing issue states, roadmap decisions, acceptance signals, and direct-versus-queued agent ownership | -| `README.md` | "Use OpenShell with Your Agent" and "Built With Agents" sections | -| `.github/ISSUE_TEMPLATE/bug_report.yml` | Skill name references in diagnostic guidance | -| `.github/ISSUE_TEMPLATE/feature_request.yml` | Skill name references in investigation guidance | -| `.github/ISSUE_TEMPLATE/config.yml` | Contact link text referencing skills | -| `.github/workflows/issue-triage.yml` | Comment text referencing skills | -| `.agents/skills/triage-issue/SKILL.md` | Skill name references in gate check and diagnosis steps | -| `skills/*/SKILL.md` | Standalone user instructions and links to documentation, included files, and related skills | -| `.agents/skills/create-github-pr/SKILL.md` | Pre-PR agent infrastructure check | -| `.agents/skills/review-github-pr/SKILL.md` | Review-time agent infrastructure check | -| `.agents/skills/build-from-issue/SKILL.md` | Label awareness and pre-commit agent infrastructure check | -| `.claude/agents/principal-engineer-reviewer.md` | Shared review-time agent infrastructure check | +## When to run -## When to Run +Run after adding, removing, or renaming a skill or crate; changing issue or PR conventions; changing development workflows; or modifying templates or agent cross references. Run before opening a PR that touches these areas. -- After adding, removing, renaming, or moving a skill in `skills/` or `.agents/skills/` -- After adding, removing, or renaming a crate in `crates/` -- After changing workflow chain relationships between skills -- After changing which product or development areas a skill covers -- After modifying issue or PR templates -- Before opening a PR that touches any of the above +## Maintenance map -## Skill Maintenance Map - -Use this map when product behavior, commands, or development workflows change. It is a routing aid, not an exhaustive dependency list. Search both `skills/` and `.agents/skills/` for the changed command, field, component, or workflow before concluding that no other skill needs an update. - -| Change area | Skills to review | +| Change | Skills to inspect | |---|---| +| Issues, triage, labels, or feature proposals | `create-github-issue`, `triage-issue`, `create-spike`, `build-from-issue` | +| PR template, vouch behavior, or review conventions | `create-github-pr`, `review-github-pr`, `build-from-issue` | +| Security assessment or remediation | `review-security-issue`, `fix-security-issue` | +| Published docs workflow | `update-docs-from-commits` | | CLI commands, flags, defaults, or workflows | `openshell-cli` | -| Sandbox policy schema, presets, or enforcement behavior | `generate-sandbox-policy`, `openshell-cli` | -| Supervisor middleware policy, registrations, runtime, or failure behavior | `generate-sandbox-policy`, `openshell-cli`, `debug-openshell-cluster` | -| Gateway deployment, Helm, runtime drivers, or health checks | `debug-openshell-cluster`, `helm-dev-environment` | -| Inference providers, native model endpoints, or migration from the retired managed endpoint | `debug-inference`, `openshell-cli`, `generate-sandbox-policy` | -| TUI architecture, navigation, data fetching, or UX | `tui-development` | -| Release artifacts or post-publish smoke coverage | `test-release-canary` | -| GitHub Actions workflows, required checks, or CI diagnostics | `watch-github-actions`; also `test-release-canary` for release smoke coverage | -| Gator harness, sandbox image, supervision, or model overrides | `launch-openshell-gator` | -| SBOM generation, dependency metadata, or license workflows | `sbom` | -| Issue templates, labels, contribution gates, or spike/build workflow | `triage-issue`, `create-spike`, `build-from-issue`, `create-github-issue` | -| PR template, review conventions, or vouch behavior | `create-github-pr`, `review-github-pr`, `build-from-issue` | -| Security review or remediation workflow | `review-security-issue`, `fix-security-issue` | -| RFC template, numbering, or lifecycle | `create-rfc` | -| Documentation structure, navigation, or doc-update workflow | `update-docs-from-commits` | -| Skills, crates, workflow chains, issue/PR templates, or agent cross-references | `sync-agent-infra` | - -## Prerequisites - -You must be in the OpenShell repository root. - -## Step 1: Inventory Current State - -Gather the source of truth for each category. - -### Skills - -List public and contributor skill directories separately: - -```bash -ls -1 skills/ -ls -1 .agents/skills/ -``` - -The directories are canonical by audience: `skills/` contains public, installable user/operator skills and `.agents/skills/` contains internal contributor workflows. Every other file must agree with both inventories. - -### Crates - -List all crate directories: - -```bash -ls -1 crates/ -``` - -### Workflow Chains - -The canonical workflow chains are defined in `AGENTS.md` under "## Workflow Chains". Read that section — it is the source of truth for skill pipelines. - -### Labels - -The canonical label set is used by skills and templates. The key labels are: `state:triage-needed`, `state:needs-info`, `state:validated`, `state:accepted`, `agent:plan-requested`, `agent:plan-ready`, `agent:implementation-requested`, `agent:in-progress`, `agent:pr-opened`, `roadmap`, `topic:security`, `good first issue`, `help wanted`, `spike`, and the relevant `area:*`, `topic:*`, `integration:*`, and `test:*` labels. Lifecycle and `agent:*` request labels gate unattended queue pickup. They do not prevent a direct user request: the agent warns about each missing or incomplete expected workflow label and continues with the requested phase without changing those labels. - -## Step 2: Check Each File for Drift - -For each file in the table above, check for the following inconsistencies: - -### `CONTRIBUTING.md` - -1. **Public skills table** — Every skill in `skills/` must appear in "Skills for Using OpenShell" and no contributor skill may appear there. -2. **Contributor skills table** — Every skill in `.agents/skills/` must appear in "Agent Skills for Contributors" and no public skill may appear there. -3. **Inventory paths** — No skill in either table should reference a directory that does not exist. -4. **Workflow chains** — Must match `AGENTS.md` workflow chains exactly. -5. **Skill references in prose** — Any named skill must exist in exactly one canonical skill directory. - -### `AGENTS.md` - -1. **Architecture overview** — Every crate in `crates/` must appear in the architecture table. The `python/`, `proto/`, `deploy/`, `.agents/` rows must also be present. -2. **Skill layout** — The architecture table must contain separate `skills/` and `.agents/skills/` rows with accurate audience descriptions. -3. **Workflow chains** — Verify each skill named in a chain exists in exactly one of the two skill directories. -4. **Issue/PR conventions** — Verify referenced skills (`create-github-issue`, `create-github-pr`, `build-from-issue`) exist. -5. **Skill maintenance pointer** — Verify it still points to `sync-agent-infra` and does not duplicate the maintenance map from this skill. - -### Issue Lifecycle Documentation - -1. **`CONTRIBUTING.md` issue lifecycle section** — State, roadmap, acceptance-signal, and agent-workflow meanings must match `AGENTS.md`. -2. **Invocation modes** — Lifecycle and `agent:*` request labels must gate unattended queue pickup without blocking a direct user request to a specific agent. -3. **Direct-mode warnings** — Guidance must require the agent to warn about each missing or incomplete expected workflow label, continue with the requested phase, and leave labels unchanged. - -### `README.md` - -1. **Public installation guidance** — The README must distinguish `skills/` from `.agents/skills/`, include `npx skills add NVIDIA/OpenShell`, and list only canonical public skills as installable. -2. **"Built With Agents"** — Contributor skill names must exist under `.agents/skills/`. Workflow descriptions should be consistent with `AGENTS.md` chains. - -### Issue Templates - -1. **`bug_report.yml`** — Must collect a User Story, Problem Statement, Impact / Why This Matters, Acceptance Criteria, Reproduction Steps, and Environment. Logs are optional and bug-specific; reporter diagnostics must not be required. -2. **`feature_request.yml`** — Must collect a User Story, Problem Statement, Impact / Why This Matters, Proposed Design, Acceptance Criteria, and Alternatives Considered. The design describes workflow and observable behavior without prescribing internal implementation; agent investigation is optional. -3. **`config.yml`** — Skill category descriptions in contact links should be accurate. - -### Issue Triage Workflow - -1. **`issue-triage.yml`** — Skill names in the redirect comment must exist. - -### Skill Cross-References - -1. **`triage-issue`** — Skills referenced in gate check and diagnosis steps must exist. -2. **`openshell-cli`** — Companion skills table entries must exist in one canonical location. -3. **`build-from-issue`** — Label names must match the project's label taxonomy. Lifecycle and request labels must gate unattended queue pickup, while direct requests warn on workflow discrepancies and continue. -4. **`create-spike`** — Reference to `build-from-issue` as next step must be accurate. -5. **`review-security-issue`** / **`fix-security-issue`** — Cross-references between the two must be accurate. -6. **PR creation and review checks** — The `create-github-pr`, `review-github-pr`, `build-from-issue`, and `principal-engineer-reviewer` references to `sync-agent-infra` must exist and use trigger conditions aligned with this skill. - -### Skill Layout, Metadata, and Portability - -1. **Placement** — The four public skills (`openshell-cli`, `generate-sandbox-policy`, `debug-inference`, and `debug-openshell-cluster`) must live only in `skills/`. Every other repository skill must live only in `.agents/skills/`. -2. **Internal metadata** — Every `.agents/skills/*/SKILL.md` must set `metadata.internal: true`. Public skills must not set internal metadata. Treat this as a discovery filter, not an access-control boundary. -3. **Unique names** — Parse the `name` field from every `SKILL.md` under both roots. Every name must be globally unique and match the documented inventory. -4. **Local references** — Every relative Markdown link and referenced file in a skill must resolve within that installed skill directory unless the reference is an explicit published URL. -5. **Canonical paths** — Contributor skills that name the source location of a public skill must use `skills//...`, never `.agents/skills//...`. -6. **Public portability** — Public skills must not require repository-relative files under `docs/`, `crates/`, `deploy/`, or `.agents/`; source builds; `mise`; or repository E2E workflows. Use installed `openshell --help` for command syntax and Markdown endpoints under `https://docs.nvidia.com/openshell/latest/` (URLs ending in `.md`) for product documentation. -7. **No canonical documentation copies** — Review public reference files and large command/schema blocks. Remove material that merely copies CLI help, policy schemas, RFCs, or published operational documentation; retain only skill-specific reasoning and worked interactions. -8. **Discovery** — Run `npx -y skills add . --list` from a clean checkout or disposable copy. It must list exactly the four public skills. Remove any generated lock file or installed directory after the check. - -## Step 3: Report Drift - -If any inconsistencies are found, report them in a structured format: - -```markdown -## Agent Infrastructure Drift Report - -### Skills Inventory -- PUBLIC ADDED (exists in skills/ but missing from CONTRIBUTING.md): -- PUBLIC REMOVED (documented as public but missing from skills/): -- CONTRIBUTOR ADDED (exists in .agents/skills/ but missing from CONTRIBUTING.md): -- CONTRIBUTOR REMOVED (documented as contributor but missing from .agents/skills/): -- METADATA/PATH/NAME ERRORS: -- OK: public and contributor skills consistent - -### Architecture Table -- ADDED (exists in crates/ but missing from AGENTS.md): -- REMOVED (in AGENTS.md but missing from crates/): -- OK: components consistent - -### Workflow Chains -- STALE: references non-existent skill -- OK: chains consistent - -### Cross-References -- : references non-existent skill -- : references non-existent label