Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,29 @@ import (
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
)

// Version forces a base-layer rebuild for provisioning changes the generated
// selector text can't otherwise capture; bump it when the contract changes.
const Version = "1"
// Version forces a base-layer rebuild for provisioning changes the view data
// can't otherwise capture (e.g. how provision.sh consumes a field); bump it
// when the contract changes. "2": selection moved into provision.sh's template.
const Version = "2"

// Fingerprint hashes the whole generated provisioning contract into the
// base-layer cache key, so any profile or init-setup change rebuilds the base.
// Fingerprint hashes the distro registry's entire contribution to the
// provisioning script — the view data, which embeds the init-setup files, the
// quoted profile scalars and the id lists. The script's structure is the raw
// provision.sh template, hashed separately by base's Hash(). %#v prints field
// names, so a field added to ProfileView is fingerprinted automatically.
func Fingerprint() string {
sum := sha256.Sum256([]byte(Version + "\x00" + ShellSelector()))
sum := sha256.Sum256([]byte(Version + "\x00" + fmt.Sprintf("%#v", NewTemplateData())))

return hex.EncodeToString(sum[:])
}

// Profile is the declared, per-family provisioning contract. IDs are the
// /etc/os-release values that map to the family; PkgQueryBody, PkgInstall,
// CARefresh and Bootstrap are shell fragments spliced into the generated
// selector (Bootstrap, if set, runs first — for premade images with no FHS
// userland yet).
// CARefresh and Bootstrap are shell fragments spliced into provision.sh's
// selection template (Bootstrap, if set, runs first — for premade images with
// no FHS userland yet).
type Profile struct {
Key string
Init InitSystem
Expand Down Expand Up @@ -175,7 +178,7 @@ var Profiles = []Profile{
},
}

// SupportedIDs returns every os-release ID the selector accepts.
// SupportedIDs returns every os-release ID the selection accepts.
func SupportedIDs() []string {
var ids []string
for _, p := range Profiles {
Expand All @@ -187,81 +190,6 @@ func SupportedIDs() []string {

// RejectedIDs are distro ids we refuse even though their ID_LIKE names a
// family we do support. Oracle Linux and Amazon Linux both declare
// ID_LIKE=fedora, so without this the ID_LIKE fallback below would quietly
// ID_LIKE=fedora, so without this provision.sh's ID_LIKE fallback would quietly
// re-admit exactly the images the rhel profile documents as out of scope.
var RejectedIDs = []string{"rhel", "ol", "amzn"}

// ShellSelector generates the POSIX-sh block provision.sh sources: it switches
// on the guest's $E2B_DISTRO_ID and defines the profile's packages, shell
// functions, init path, time-sync unit, admin group and CA handling.
//
// An unknown id retries each $E2B_ID_LIKE token in order and provisions the
// first matching family — best effort, with a customer-visible warning.
// RejectedIDs, ids matching nothing, and images without /etc/os-release exit 1.
func ShellSelector() string {
var b strings.Builder

// Selection lives in a function so it can be retried per ID_LIKE token.
// Assignments and function definitions inside a POSIX-sh function are
// global, so the caller sees the profile the same way it always has.
// The match is reported via e2b_profile_matched, not the return status —
// a function called as an if-condition runs with errexit suppressed,
// which would swallow Bootstrap failures inside a matched arm.
b.WriteString("e2b_select_profile() {\n")
b.WriteString(" e2b_profile_matched=\n")
b.WriteString(` case "$1" in` + "\n")
for _, p := range Profiles {
fmt.Fprintf(&b, " %s)\n", strings.Join(p.IDs, "|"))
if p.Bootstrap != "" {
fmt.Fprintf(&b, " %s\n", p.Bootstrap)
}
fmt.Fprintf(&b, " E2B_PACKAGES=%q\n", strings.Join(p.Packages, " "))
fmt.Fprintf(&b, " e2b_pkg_query() { %s; }\n", p.PkgQueryBody)
fmt.Fprintf(&b, " e2b_pkg_install() { %s; }\n", p.PkgInstall)
fmt.Fprintf(&b, " E2B_INIT_BIN=%q\n", p.InitBinary)
fmt.Fprintf(&b, " E2B_TIMESYNC_UNIT=%q\n", p.TimeSyncUnit)
fmt.Fprintf(&b, " E2B_SSH_UNIT=%q\n", p.SSHUnit)
fmt.Fprintf(&b, " E2B_ADMIN_GROUP=%q\n", p.AdminGroup)
fmt.Fprintf(&b, " E2B_CA_BUNDLE=%q\n", p.CABundle)
fmt.Fprintf(&b, " e2b_ca_refresh() { %s; }\n", p.CARefresh)
fmt.Fprintf(&b, " E2B_INIT_SYSTEM=%q\n", p.Init)
fmt.Fprintf(&b, " e2b_init_setup() {\n%s\n }\n", indentBlock(initSetup[p.Init], " "))
fmt.Fprintf(&b, " e2b_profile_matched=1\n")
fmt.Fprintf(&b, " ;;\n")
}
fmt.Fprintf(&b, " *)\n ;;\n")
b.WriteString(" esac\n}\n\n")

b.WriteString(`e2b_select_profile "$E2B_DISTRO_ID"` + "\n")
b.WriteString(`if [ -z "$e2b_profile_matched" ]; then` + "\n")

// Deliberate rejections are checked before the fallback, so they keep
// failing fast with their own reason instead of being matched by ID_LIKE.
fmt.Fprintf(&b, " case \"$E2B_DISTRO_ID\" in\n")
fmt.Fprintf(&b, " %s)\n", strings.Join(RejectedIDs, "|"))
fmt.Fprintf(&b, " echo \"[provision] ERROR: base image distribution ID='$E2B_DISTRO_ID' is not supported.\" >&2\n")
fmt.Fprintf(&b, " echo \"[provision] Sandboxes boot E2B's kernel, so the kABI, signed modules and SELinux these images are chosen for are unavailable.\" >&2\n")
fmt.Fprintf(&b, " exit 1\n")
fmt.Fprintf(&b, " ;;\n")
fmt.Fprintf(&b, " esac\n")

b.WriteString(" e2b_like_match=\n")
b.WriteString(` for e2b_like in $E2B_ID_LIKE; do` + "\n")
b.WriteString(` e2b_select_profile "$e2b_like"` + "\n")
b.WriteString(` if [ -n "$e2b_profile_matched" ]; then` + "\n")
b.WriteString(" e2b_like_match=$e2b_like\n")
b.WriteString(" break\n")
b.WriteString(" fi\n")
b.WriteString(" done\n")

b.WriteString(` if [ -z "$e2b_like_match" ]; then` + "\n")
fmt.Fprintf(&b, " echo \"[provision] ERROR: unsupported base image distribution: ID='${E2B_DISTRO_ID:-unknown}'.\" >&2\n")
fmt.Fprintf(&b, " echo \"[provision] E2B template builds support: %s.\" >&2\n", strings.Join(SupportedIDs(), ", "))
fmt.Fprintf(&b, " exit 1\n")
b.WriteString(" fi\n")

fmt.Fprintf(&b, " echo \"[provision] WARNING: base image distribution ID='$E2B_DISTRO_ID' is not officially supported; provisioning it as '$e2b_like_match' from ID_LIKE. This is best effort and untested.\" >&2\n")
b.WriteString("fi\n")

return b.String()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package distro
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
"testing"
)
Expand Down Expand Up @@ -82,40 +83,11 @@ func TestRhelAllowErasingFollowsSubcommand(t *testing.T) {
}
}

// The generated selector keys on the DECLARED distro id, never on which
// package-manager binary happens to exist.
func TestSelectorNoPackageManagerProbing(t *testing.T) {
// Alpine is supported via the OpenRC track — and it must be the OpenRC
// profile, never folded into a systemd family. (Selection-text assertions
// live in base/provision_test.go against the rendered script.)
func TestAlpineIsOpenRC(t *testing.T) {
t.Parallel()
sel := ShellSelector()
for _, bad := range []string{
"command -v apt-get", "command -v dnf", "command -v yum",
"command -v microdnf", "command -v pacman", "PKG_FAMILY",
} {
if strings.Contains(sel, bad) {
t.Errorf("selector leaked package-manager probing: %q", bad)
}
}
if !strings.Contains(sel, `case "$E2B_DISTRO_ID" in`) {
t.Error("selector must switch on $E2B_DISTRO_ID (declared distro identity)")
}
}

// Every supported id gets a case arm; an unknown id hits the failing default.
func TestSelectorCoversIDsAndRejects(t *testing.T) {
t.Parallel()
sel := ShellSelector()
for _, id := range SupportedIDs() {
if !strings.Contains(sel, id) {
t.Errorf("selector missing arm for supported id %q", id)
}
}
for _, want := range []string{"*)", "unsupported base image", "exit 1"} {
if !strings.Contains(sel, want) {
t.Errorf("selector missing fast-reject piece %q", want)
}
}
// Alpine is supported via the OpenRC track — and it must be the OpenRC
// profile, never folded into a systemd family.
alpine := profileByKey(t, "alpine")
if alpine.Init != InitOpenRC {
t.Errorf("alpine must be the OpenRC profile, got init %q", alpine.Init)
Expand Down Expand Up @@ -145,16 +117,6 @@ func TestInitSystemsDeclaredAndCoherent(t *testing.T) {
}
}
}
sel := ShellSelector()
if !strings.Contains(sel, "e2b_init_setup() {") {
t.Error("selector must define e2b_init_setup()")
}
// The OpenRC boot chain pieces the alpine arm must carry.
for _, want := range []string{"/etc/inittab", "rc-update add envd default", "openrc sysinit"} {
if !strings.Contains(sel, want) {
t.Errorf("selector missing OpenRC boot piece %q", want)
}
}
}

// The per-profile fragments are spliced into single-line generated shell
Expand Down Expand Up @@ -233,18 +195,19 @@ func TestOpenRCDisablesChronySeccompRegardlessOfSource(t *testing.T) {
}
}

// The cache fingerprint must cover the whole generated provisioning contract:
// stable across calls, and carrying both the selector text and the explicit
// Version (a profile change must rotate the base-layer cache key).
// The cache fingerprint must cover the registry's whole contribution to
// provisioning: stable across calls, and carrying both the view data and the
// explicit Version (a profile or init-file change must rotate the base-layer
// cache key; the script structure is hashed separately in base).
func TestFingerprintStableAndVersioned(t *testing.T) {
t.Parallel()
a, b := Fingerprint(), Fingerprint()
if a != b || len(a) != 64 {
t.Errorf("fingerprint must be a stable sha256 hex: %q vs %q", a, b)
}
want := sha256.Sum256([]byte(Version + "\x00" + ShellSelector()))
want := sha256.Sum256([]byte(Version + "\x00" + fmt.Sprintf("%#v", NewTemplateData())))
if a != hex.EncodeToString(want[:]) {
t.Error("fingerprint must hash Version + selector text")
t.Error("fingerprint must hash Version + template view data")
}
}

Expand Down Expand Up @@ -279,46 +242,49 @@ func TestKernelDependentIDsAreRejected(t *testing.T) {
}
}

// An id we don't know falls back to ID_LIKE with a warning instead of failing:
// switching provisioning to the declared id silently dropped every Debian
// derivative (Kali declares ID=kali ID_LIKE=debian) that used to work back when
// we probed for a package manager.
func TestUnknownIDFallsBackToIDLike(t *testing.T) {
// Quoted view fields are spliced into sh double quotes via Go %q, which only
// matches sh semantics for values free of `"`, `\`, `$`, backticks and control
// characters (%q passes $ and backtick through unescaped — sh would expand
// them — and escapes control chars into sequences sh reads literally).
func TestQuotedFieldsAreShellSafe(t *testing.T) {
t.Parallel()
sel := ShellSelector()
if !strings.Contains(sel, "e2b_select_profile") {
t.Error("selection must be a function so it can be retried per ID_LIKE token")
}
if !strings.Contains(sel, "for e2b_like in $E2B_ID_LIKE; do") {
t.Error("selector must retry each ID_LIKE token")
}
if !strings.Contains(sel, "WARNING") {
t.Error("an ID_LIKE match must warn, not pass silently")
}
// Nothing matched is still fatal — better than provisioning a guessed family.
if !strings.Contains(sel, "unsupported base image distribution") {
t.Error("an id matching neither ID nor ID_LIKE must still fail")
}
// An if-condition call runs the function body with errexit suppressed.
if strings.Contains(sel, "if e2b_select_profile") || strings.Contains(sel, "if ! e2b_select_profile") {
t.Error("e2b_select_profile must not be invoked as an if-condition (errexit suppression)")
}
if !strings.Contains(sel, "e2b_profile_matched=1") {
t.Error("a matched profile arm must set e2b_profile_matched")
for _, p := range Profiles {
fields := map[string]string{
"Packages": strings.Join(p.Packages, " "),
"InitBinary": p.InitBinary,
"TimeSyncUnit": p.TimeSyncUnit,
"SSHUnit": p.SSHUnit,
"AdminGroup": p.AdminGroup,
"CABundle": p.CABundle,
"InitSystem": string(p.Init),
}
for name, v := range fields {
if fmt.Sprintf("%q", v) != `"`+v+`"` {
t.Errorf("profile %q field %s needs %%q escaping — not plain-sh-quotable: %q", p.Key, name, v)
}
if strings.ContainsAny(v, "$`") {
t.Errorf("profile %q field %s contains sh-expandable characters: %q", p.Key, name, v)
}
}
}
}

// ID_LIKE must not re-admit the ids the rhel profile documents as out of scope:
// Oracle and Amazon Linux both declare ID_LIKE=fedora.
func TestRejectedIDsAreNotReachableViaIDLike(t *testing.T) {
// The view hands provision.sh ready-made patterns and lists.
func TestTemplateDataJoins(t *testing.T) {
t.Parallel()
sel := ShellSelector()
guard := strings.Join(RejectedIDs, "|")
if !strings.Contains(sel, guard) {
t.Errorf("selector must guard rejected ids (%s) before the ID_LIKE fallback", guard)
data := NewTemplateData()
if len(data.Profiles) != len(Profiles) {
t.Fatalf("view has %d profiles, registry %d", len(data.Profiles), len(Profiles))
}
for i, p := range Profiles {
if data.Profiles[i].CasePattern != strings.Join(p.IDs, "|") {
t.Errorf("profile %q CasePattern mismatch: %q", p.Key, data.Profiles[i].CasePattern)
}
}
if data.RejectedIDsPattern != strings.Join(RejectedIDs, "|") {
t.Errorf("RejectedIDsPattern mismatch: %q", data.RejectedIDsPattern)
}
// The guard has to come first, or ID_LIKE=fedora would match them.
if strings.Index(sel, guard) > strings.Index(sel, "E2B_ID_LIKE") {
t.Error("the rejected-id guard must precede the ID_LIKE fallback")
if data.SupportedIDs != strings.Join(SupportedIDs(), ", ") {
t.Errorf("SupportedIDs mismatch: %q", data.SupportedIDs)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# NixOS is declaratively configured; drop the baked systemd units so
# activation can own /etc/systemd/system as a store symlink (foreign files
# there make systemd boot with no units at all).
echo "NixOS is declaratively configured; removing the baked systemd drop-ins"
rm -rf /etc/systemd/system
Loading
Loading