Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions internal/api/sample.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package api

import (
"net/http"
"os"
"path/filepath"
)

// The bundled "try our sample" artifact is the aegis security probe. It is
// served publicly so the web UI empty state can drop it into the validate form
// and run a real compatibility matrix. aegis uses a bloom-filter map
// (BPF_MAP_TYPE_BLOOM_FILTER, kernel 5.16+), so it loads on >= 5.16 and fails
// below — a precise, real compatibility boundary. See examples/aegis-live/.

// sampleArtifactPath returns the path to the bundled sample .bpf.o, overridable
// with BPFCOMPAT_SAMPLE_ARTIFACT for non-repo-root deployments (e.g. the demo).
func sampleArtifactPath() string {
if p := os.Getenv("BPFCOMPAT_SAMPLE_ARTIFACT"); p != "" {
return p
}
return filepath.FromSlash("examples/aegis-live/aegis.bpf.o")
}

// handleSampleArtifact serves the bundled aegis sample object for the
// "Try our aegis sample" empty-state button. Intentionally public (no auth):
// it is a published sample artifact, not user data.
func (s *Server) handleSampleArtifact(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeError(w, http.StatusMethodNotAllowed, "method not allowed")
return
}
// Fixed, operator-controlled sample path (default or BPFCOMPAT_SAMPLE_ARTIFACT).
path := filepath.Clean(sampleArtifactPath())
data, err := os.ReadFile(path) // #nosec G304 -- fixed sample path, not user input
if err != nil {
writeError(w, http.StatusNotFound, "sample artifact not available")
return
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", `attachment; filename="aegis.bpf.o"`)
w.Header().Set("Cache-Control", "public, max-age=3600")
_, _ = w.Write(data)
}
1 change: 1 addition & 0 deletions internal/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,7 @@ func (s *Server) serve(ctx context.Context) error {
registerAPIRoute(mux, "/health", s.handleHealth)
registerAPIRoute(mux, "/config", s.handleConfig)
registerAPIRoute(mux, "/profiles", s.handleProfiles)
registerAPIRoute(mux, "/sample/aegis/artifact", s.handleSampleArtifact)
registerAPIRoute(mux, "/validate/start", s.handleValidateStart)
registerAPIRoute(mux, "/validate/status", s.handleValidateStatus)
registerAPIRoute(mux, "/validate", s.handleValidate)
Expand Down
60 changes: 58 additions & 2 deletions internal/api/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,9 +282,8 @@ const uiHTML = `<!doctype html>
.results {
padding: 12px 14px;
display: grid;
grid-template-rows: auto auto auto 1fr auto;
grid-template-rows: repeat(5, auto);
gap: 10px;
height: calc(100% - 52px);
box-sizing: border-box;
}
.progress-wrap {
Expand Down Expand Up @@ -854,6 +853,10 @@ const uiHTML = `<!doctype html>
<div id="artifactMode">
<label>Artifact File</label>
<input id="artifactFile" type="file">
<div class="hint" id="trySampleHint">
Don't have a <code>.bpf.o</code>?
<button type="button" class="secondary" id="trySampleBtn">Try our aegis sample &rarr;</button>
</div>
</div>
<div id="sourceMode" class="hidden">
<label>Source File</label>
Expand Down Expand Up @@ -1766,6 +1769,59 @@ programs:
byId("modeSource").addEventListener("click", () => switchMode("source"));
byId("intentLoadAttach").addEventListener("click", () => switchTestIntent("load_attach"));
byId("intentLoadOnly").addEventListener("click", () => switchTestIntent("load_only"));

// "Try our aegis sample": load the bundled artifact into the form and run a
// real validation across a kernel spread that crosses aegis's 5.16 boundary
// (it uses a bloom-filter map). One click -> artifact filled below -> run.
byId("trySampleBtn").addEventListener("click", async () => {
const btn = byId("trySampleBtn");
const original = btn.textContent;
btn.disabled = true;
btn.textContent = "Loading aegis sample...";
try {
const res = await fetch("/api/v1/sample/aegis/artifact");
if (!res.ok) throw new Error("sample unavailable (HTTP " + res.status + ")");
const blob = await res.blob();
const file = new File([blob], "aegis.bpf.o", { type: "application/octet-stream" });
const dt = new DataTransfer();
dt.items.add(file);
const input = byId("artifactFile");
input.files = dt.files;
input.dispatchEvent(new Event("change", { bubbles: true }));
byId("artifactName").value = "aegis";

// Reset to a clean slate so only the boundary spread runs (no arm64 /
// kernel-sweep variants from the default selection).
document.querySelectorAll("input[data-kind='include']").forEach((x) => { x.checked = false; });
document.querySelectorAll("input[data-kind='required']").forEach((x) => { x.checked = false; x.disabled = true; });

// Boundary spread: 5.4/5.15 fail (no bloom map), 6.1/6.8 pass (required).
const include = ["ubuntu-20.04-5.4", "ubuntu-22.04-5.15", "debian-12-6.1", "ubuntu-24.04-6.8"];
const required = ["debian-12-6.1", "ubuntu-24.04-6.8"];
include.forEach((id) => {
const inc = document.querySelector("input[data-kind='include'][data-id='" + id + "']");
if (!inc || inc.disabled) return;
inc.checked = true;
inc.dispatchEvent(new Event("change", { bubbles: true }));
if (required.includes(id)) {
const req = document.querySelector("input[data-kind='required'][data-id='" + id + "']");
if (req) {
req.disabled = false;
req.checked = true;
req.dispatchEvent(new Event("change", { bubbles: true }));
}
}
});

btn.textContent = original;
btn.disabled = false;
byId("runBtn").click();
} catch (e) {
btn.textContent = original;
btn.disabled = false;
alert("Could not load the aegis sample: " + e.message);
}
});
runtimeModeButtons.probe.addEventListener("click", () => setRuntimeMode("probe"));
runtimeModeButtons.select.addEventListener("click", () => setRuntimeMode("select"));
runtimeModeButtons.fetch.addEventListener("click", () => setRuntimeMode("fetch"));
Expand Down
20 changes: 20 additions & 0 deletions matrices/aegis-sample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: aegis-sample
# Boundary-spanning matrix for the "try our aegis sample" demo. aegis uses a BPF
# bloom-filter map ("deny_bloom", BPF_MAP_TYPE_BLOOM_FILTER) that only exists on
# kernel >= 5.16, so it exercises a precise, real compatibility boundary:
# - ubuntu-20.04-5.4 : < 5.16 -> map create fails (UNSUPPORTED_MAP_TYPE) [expected]
# - ubuntu-22.04-5.15: < 5.16 -> map create fails (UNSUPPORTED_MAP_TYPE) [expected]
# - debian-12-6.1 : >= 5.16 -> loads + attaches -> pass
# - ubuntu-24.04-6.8 : >= 5.16 -> loads + attaches -> pass
# The < 5.16 rows are not "required": they are the teaching rows showing the
# tool catching a genuine incompatibility, while the gate is green for the
# kernels aegis actually supports.
profiles:
- id: ubuntu-20.04-5.4
required: false
- id: ubuntu-22.04-5.15
required: false
- id: debian-12-6.1
required: true
- id: ubuntu-24.04-6.8
required: true
Loading