From 91102510cb522eb524835b3b3ddf0ec49684b3e6 Mon Sep 17 00:00:00 2001 From: Lior Lieberman Date: Mon, 19 Oct 2026 11:06:40 -0700 Subject: [PATCH] e2e: remove dead helpers and the broken verify-egress-demo.sh hack/verify-egress-demo.sh is unreferenced and cannot pass since the egress gateway began enforcing EgressPolicy: it never creates a policy, so the actor gets no tunnel. demos/egress/test-egress.sh covers the same path. FColorf and FColorfln have no callers, fixtureSubstitutions sets a ${TEMPLATE_TRUST_BUNDLE} placeholder no pool manifest carries (the template renderer sets its own), and two suites wrap RunTestMain in helpers that do nothing. hack/cleanup-e2e.sh failed under bash 3.2's nounset when no context was given, because it expanded an empty kubectl_args array. The security suite's runbook names the real worker container, labels and paths, and no longer says a failed run keeps its namespaces: they come from fixture manifests whose cleanup runs pass or fail. Smaller doc fixes: a fixture name in the e2e README, a typo, a stale fixture comment, and env_test uses t.Setenv. --- hack/cleanup-e2e.sh | 4 +- hack/verify-egress-demo.sh | 88 ------------------- internal/e2e/README.md | 2 +- internal/e2e/env_test.go | 25 +----- internal/e2e/fixtures/probe/probe.yaml.tmpl | 4 +- internal/e2e/namespace.go | 2 +- internal/e2e/pretty.go | 15 +--- internal/e2e/pretty_test.go | 42 --------- internal/e2e/sandbox.go | 5 +- .../e2e/suites/egressauthz/testmain_test.go | 19 +--- .../e2e/suites/networkpolicy/testmain_test.go | 6 +- internal/e2e/suites/security/README.md | 18 ++-- 12 files changed, 22 insertions(+), 208 deletions(-) delete mode 100755 hack/verify-egress-demo.sh diff --git a/hack/cleanup-e2e.sh b/hack/cleanup-e2e.sh index 2c96ca35e5..d3dd69a6b3 100755 --- a/hack/cleanup-e2e.sh +++ b/hack/cleanup-e2e.sh @@ -41,7 +41,7 @@ fi # The label CreateNamespace stamps on every namespace the suites create. selector="ate.dev/e2e" -leftover="$(kubectl "${kubectl_args[@]}" get namespaces -l "${selector}" -o name)" +leftover="$(kubectl ${kubectl_args[@]+"${kubectl_args[@]}"} get namespaces -l "${selector}" -o name)" if [[ -z "${leftover}" ]]; then echo "No leftover e2e namespaces." exit 0 @@ -49,4 +49,4 @@ fi echo "Deleting leftover e2e namespaces:" echo "${leftover}" -kubectl "${kubectl_args[@]}" delete namespaces -l "${selector}" --ignore-not-found +kubectl ${kubectl_args[@]+"${kubectl_args[@]}"} delete namespaces -l "${selector}" --ignore-not-found diff --git a/hack/verify-egress-demo.sh b/hack/verify-egress-demo.sh deleted file mode 100755 index 8daefade2c..0000000000 --- a/hack/verify-egress-demo.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env bash - -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Preconditions: -# hack/create-kind-cluster.sh -# hack/install-ate-kind.sh --deploy-demo-egress -# -# The egress demo Actor accepts {"url":"..."} and performs an HTTP GET. With -# egress turned on, the Actor's outbound TCP is nftables-REDIRECTed -# into atunnel, wrapped in mTLS + HTTP CONNECT, and sent to the egress gateway, -# which terminates CONNECT and tunnels to the real destination. This script -# drives that path and shows the ext_proc authentication log proving the actor's -# client certificate + CONNECT authority were seen. -set -o errexit -o nounset -o pipefail -ROOT="$(git rev-parse --show-toplevel)"; cd "${ROOT}" - -CTX="${KUBECTL_CONTEXT:-kind-kind}" -K="kubectl --context ${CTX}" -# The actor lives in the demo's atespace: --template resolves the -# template by name within the actor's own atespace. -ATESPACE="${ATESPACE:-ate-demo-egress}" -ACTOR="${ACTOR:-egress-demo}" -TARGET_URL="${TARGET_URL:-http://example.com/}" - -echo "== gateway should be running ==" -${K} -n ate-system rollout status deployment/atenet-egress --timeout=120s - -echo "== create atespace + actor ==" -kubectl-ate --context "${CTX}" create atespace "${ATESPACE}" 2>/dev/null || true -kubectl-ate --context "${CTX}" create actor "${ACTOR}" \ - --atespace "${ATESPACE}" --template egress 2>/dev/null || true -# The router resumes the actor on demand; give the control plane a beat to -# register it before driving traffic. -sleep 10 - -echo "== snapshot gateway authentication log offset ==" -BEFORE=$(${K} -n ate-system logs deployment/atenet-egress -c ext-proc --tail=-1 2>/dev/null | wc -l | tr -d ' ') - -echo "== drive actor egress: GET ${TARGET_URL} via the actor ==" -${K} -n ate-system port-forward service/atenet-router 18000:80 >/tmp/pf.log 2>&1 & -PF=$!; trap 'kill ${PF} 2>/dev/null || true' EXIT -sleep 3 -RESP=$(curl -s -o /dev/null -w "%{http_code}" -X POST http://localhost:18000/ \ - -H "ate-target-actor: ${ATESPACE}/${ACTOR}" \ - -H 'Content-Type: application/json' \ - -d "{\"url\":\"${TARGET_URL}\"}") || true -echo "actor round-trip HTTP ${RESP} (200 = the actor fetched ${TARGET_URL} through egress)" - -echo "== NEW egress tunnel log lines (proof of CONNECT+mTLS+identity+policy) ==" -# The CONNECT can land seconds after the actor's response, so we poll. And the -# Actor's HTTP client keeps the -# tunnel alive: a repeat fetch to a host it already reached rides the open tunnel -# and produces no new authentication entry at all, so a run against a warm actor -# would fail even though egress is working. Fall back to any tunnel already open -# for this actor before declaring failure. -NEW="" -for _ in $(seq 1 15); do - NEW=$(${K} -n ate-system logs deployment/atenet-egress -c ext-proc --tail=-1 2>/dev/null \ - | tail -n +"$((BEFORE + 1))" | grep 'egress tunnel opened' || true) - [ -n "${NEW}" ] && break - sleep 2 -done -if [ -n "${NEW}" ]; then - echo "${NEW}" -elif ${K} -n ate-system logs deployment/atenet-egress -c ext-proc --tail=-1 2>/dev/null \ - | grep 'egress tunnel opened' | grep -q "${ATESPACE}.*${ACTOR}\|${ACTOR}.*${ATESPACE}"; then - echo " no new CONNECT — the actor reused an already-open tunnel; its existing entries:" - ${K} -n ate-system logs deployment/atenet-egress -c ext-proc --tail=-1 \ - | grep 'egress tunnel opened' | grep "${ATESPACE}.*${ACTOR}\|${ACTOR}.*${ATESPACE}" | tail -3 -else - echo "!! no egress tunnel lines for ${ATESPACE}/${ACTOR} — dumping recent ext_proc logs:" - ${K} -n ate-system logs deployment/atenet-egress -c ext-proc --tail=20 - exit 1 -fi -echo "== PASS: actor egress traversed the egress gateway ==" diff --git a/internal/e2e/README.md b/internal/e2e/README.md index 7b72641665..e0ff0b16ff 100644 --- a/internal/e2e/README.md +++ b/internal/e2e/README.md @@ -29,7 +29,7 @@ what CI installs on the envoy lane; see `internal/e2e/suites/egresscredinject`. The suites are runtime-agnostic: the same tests run against gVisor and against the micro-VM (kata + cloud-hypervisor) sandbox class. `E2E_SANDBOX_CLASS` selects which, by repointing every fixture at its variant --- see -`e2e.CounterFixture`, `e2e.EgressFixture` and `e2e.RenderFixtureManifest` in +`e2e.SubstrateCounterFixture`, `e2e.EgressFixture` and `e2e.RenderFixtureManifest` in [sandbox.go](sandbox.go). Unset means gVisor. ```shell diff --git a/internal/e2e/env_test.go b/internal/e2e/env_test.go index c15e44afb1..c3c8cb2e57 100644 --- a/internal/e2e/env_test.go +++ b/internal/e2e/env_test.go @@ -15,33 +15,14 @@ package e2e import ( - "os" "reflect" "testing" ) func TestCheckEnv(t *testing.T) { - // Preserve original env for clean testing environment, and restore it afterwards. - // Since CheckEnv reads actual environment variables, we will back up and restore any keys we modify. - testKeys := []string{"TEST_ENV_FOO", "TEST_ENV_BAR", "TEST_ENV_EMPTY"} - originalVals := make(map[string]string) - for _, key := range testKeys { - if val, ok := os.LookupEnv(key); ok { - originalVals[key] = val - } - defer func(k string) { - if val, ok := originalVals[k]; ok { - os.Setenv(k, val) - } else { - os.Unsetenv(k) - } - }(key) - } - - // Set up our test environment - os.Setenv("TEST_ENV_FOO", "value_foo") - os.Setenv("TEST_ENV_BAR", "value_bar") - os.Setenv("TEST_ENV_EMPTY", "") + t.Setenv("TEST_ENV_FOO", "value_foo") + t.Setenv("TEST_ENV_BAR", "value_bar") + t.Setenv("TEST_ENV_EMPTY", "") tests := []struct { name string diff --git a/internal/e2e/fixtures/probe/probe.yaml.tmpl b/internal/e2e/fixtures/probe/probe.yaml.tmpl index 984925b566..c75a0d9df4 100644 --- a/internal/e2e/fixtures/probe/probe.yaml.tmpl +++ b/internal/e2e/fixtures/probe/probe.yaml.tmpl @@ -26,8 +26,8 @@ kind: Namespace metadata: name: ate-e2e-probe${FIXTURE_SUFFIX} labels: - # Lets hack/cleanup-e2e.sh find and delete this namespace when a failed - # test leaves the fixture behind for debugging. + # Lets hack/cleanup-e2e.sh find and delete this namespace when an + # interrupted run leaves the fixture behind. ate.dev/e2e: "true" --- diff --git a/internal/e2e/namespace.go b/internal/e2e/namespace.go index 72bbcd9f29..71199f190e 100644 --- a/internal/e2e/namespace.go +++ b/internal/e2e/namespace.go @@ -41,7 +41,7 @@ var ( rnd = rand.New(rand.NewSource(time.Now().UnixNano())) ) -// randomnamepaceName returns a random namespace name. +// randomNamespaceName returns a random namespace name. // Format is "aaaa-ddd" where a is a random letter and d is a random digit. func randomNamespaceName() string { const letters = "abcdefghijklmnopqrstuvwxyz" diff --git a/internal/e2e/pretty.go b/internal/e2e/pretty.go index f7f4867e49..c60566ac7e 100644 --- a/internal/e2e/pretty.go +++ b/internal/e2e/pretty.go @@ -22,8 +22,8 @@ import ( "regexp" ) -// noColor() disables colors for tests running in environments without colored output. var ( + // NoColor disables colored output, for environments that cannot show it. NoColor bool // envNoColor (see https://no-color.org/) envNoColor bool @@ -71,6 +71,8 @@ func init() { envNoColor = os.Getenv("NO_COLOR") != "" } +// noColor reports whether to disable colors, for tests running in +// environments without colored output. func noColor() bool { return NoColor || envNoColor } @@ -93,17 +95,6 @@ func Colorf(format string, a ...any) string { return str } -// FColorf writes a formatted string with color directives to the given writer. -func FColorf(f io.Writer, format string, a ...any) error { - _, err := fmt.Fprint(f, Colorf(format, a...)) - return err -} - -// FColorfln writes a formatted string with color directives to the given writer, followed by a newline. -func FColorfln(f io.Writer, format string, a ...any) error { - return FColorf(f, format+"\n", a...) -} - // ColorWriter wraps an io.Writer and forces all writes to be colored with the given ANSI code. // It respects noColor(). type ColorWriter struct { diff --git a/internal/e2e/pretty_test.go b/internal/e2e/pretty_test.go index fffa8e68dd..ce238c7af5 100644 --- a/internal/e2e/pretty_test.go +++ b/internal/e2e/pretty_test.go @@ -109,48 +109,6 @@ func TestColorf(t *testing.T) { } } -func TestFColorfAndFColorfln(t *testing.T) { - t.Run("FColorf", func(t *testing.T) { - var buf bytes.Buffer - NoColor = false - err := FColorf(&buf, "Hello %s", "world") - if err != nil { - t.Fatalf("FColorf failed: %v", err) - } - want := "Hello \033[32mworld\033[0m" - if buf.String() != want { - t.Errorf("FColorf output = %q, want %q", buf.String(), want) - } - }) - - t.Run("FColorfln", func(t *testing.T) { - var buf bytes.Buffer - NoColor = false - err := FColorfln(&buf, "Hello %s", "world") - if err != nil { - t.Fatalf("FColorfln failed: %v", err) - } - want := "Hello \033[32mworld\033[0m\n" - if buf.String() != want { - t.Errorf("FColorfln output = %q, want %q", buf.String(), want) - } - }) - - t.Run("FColorf no color", func(t *testing.T) { - var buf bytes.Buffer - NoColor = true - defer func() { NoColor = false }() - err := FColorf(&buf, "Hello %s", "world") - if err != nil { - t.Fatalf("FColorf failed: %v", err) - } - want := "Hello world" - if buf.String() != want { - t.Errorf("FColorf output = %q, want %q", buf.String(), want) - } - }) -} - func TestColorWriter(t *testing.T) { tests := []struct { name string diff --git a/internal/e2e/sandbox.go b/internal/e2e/sandbox.go index 878c155bc0..f53ce8d88d 100644 --- a/internal/e2e/sandbox.go +++ b/internal/e2e/sandbox.go @@ -39,8 +39,7 @@ func SandboxClass() string { return os.Getenv(sandboxClassEnv) } func IsMicroVM() bool { return SandboxClass() == SandboxClassMicroVM } // Fixture identifies an installed WorkerPool + ActorTemplate pair (both carry -// the same name) that suites either create Actors from directly or copy the -// resolved runtime — sandbox class, worker image, container images — out of. +// the same name) that suites create Actors from. type Fixture struct { Namespace string Name string @@ -185,8 +184,6 @@ func fixtureSubstitutions(bucket, name string) (inline, blocks map[string]string } blocks = map[string]string{ "${WORKERPOOL_RUNTIME}": " sandboxClasses:\n - name: gvisor", - // Off unless the caller opts in; see WithTrustBundle. - "${TEMPLATE_TRUST_BUNDLE}": "", } if !IsMicroVM() { return inline, blocks diff --git a/internal/e2e/suites/egressauthz/testmain_test.go b/internal/e2e/suites/egressauthz/testmain_test.go index 439325016e..4b047a1bed 100644 --- a/internal/e2e/suites/egressauthz/testmain_test.go +++ b/internal/e2e/suites/egressauthz/testmain_test.go @@ -21,21 +21,4 @@ import ( "go.ate.dev/substrate/internal/e2e" ) -// Setup runs before the suite. The probe is created on first use, in the -// randomized namespace the suite creates, so there is nothing to do here. -func Setup() {} - -// Teardown runs after the suite. -func Teardown() {} - -func run(m *testing.M) int { - Setup() - defer Teardown() - - // return allows the deferred Teardown to run. - return e2e.RunTestMain(m) -} - -func TestMain(m *testing.M) { - os.Exit(run(m)) -} +func TestMain(m *testing.M) { os.Exit(e2e.RunTestMain(m)) } diff --git a/internal/e2e/suites/networkpolicy/testmain_test.go b/internal/e2e/suites/networkpolicy/testmain_test.go index 18a7e30e7b..4f26463885 100644 --- a/internal/e2e/suites/networkpolicy/testmain_test.go +++ b/internal/e2e/suites/networkpolicy/testmain_test.go @@ -21,8 +21,4 @@ import ( "go.ate.dev/substrate/internal/e2e" ) -func run(m *testing.M) int { - return e2e.RunTestMain(m) -} - -func TestMain(m *testing.M) { os.Exit(run(m)) } +func TestMain(m *testing.M) { os.Exit(e2e.RunTestMain(m)) } diff --git a/internal/e2e/suites/security/README.md b/internal/e2e/suites/security/README.md index e41410f208..313e3c4ce6 100644 --- a/internal/e2e/suites/security/README.md +++ b/internal/e2e/suites/security/README.md @@ -37,12 +37,8 @@ hack/run-e2e-kind.sh ./internal/e2e/suites/security -v \ Note the setup (victim run/write/suspend, attacker reuse) runs regardless — the subtest filter only selects which assertions execute. -4. On failure, a failed suite keeps its namespaces so you can read the worker logs: - - -kubectl get ns -l ate.dev/e2e -kubectl -n logs -c ateworker -KIND_CLUSTER_NAME=kind hack/cleanup-e2e.sh # reclaim when done +4. The suite deletes its namespaces when it ends, pass or fail, so read the +worker logs while it runs (see below). @@ -84,7 +80,7 @@ run with `E2E_SANDBOX_CLASS=microvm`. Neither matters for this suite. Watch the lifecycle (start these first, before/at test launch) # The two atespace namespaces appear and vanish — this is the run's heartbeat -kubectl get ns -l ate.dev/e2e -w +kubectl get ns -w | grep ate-e2e-sec # The one shared worker pod: watch the victim's pod get reused, not replaced kubectl get pods -n ate-e2e-sec-a -w @@ -106,14 +102,14 @@ The shared worker pod on the host kubectl get pods -n ate-e2e-sec-a -o wide # worker logs — the sandbox lifecycle, checkpoints, resumes -kubectl logs -n ate-e2e-sec-a -l app=ateworker -c ateworker -f +kubectl logs -n ate-e2e-sec-a -l ate.dev/worker-pool=shared -c worker -f # The on-host per-actor layout the host-state probes assert against. # Two UID-keyed durable dirs (victim's + attacker's) live side by side here: -POD=$(kubectl get pods -n ate-e2e-sec-a -l app=ateworker -o name | head -1) -kubectl exec -n ate-e2e-sec-a "$POD" -c ateworker -- ls -la /var/lib/ateworker-gvisor/actors +POD=$(kubectl get pods -n ate-e2e-sec-a -l ate.dev/worker-pool=shared -o name | head -1) +kubectl exec -n ate-e2e-sec-a "$POD" -c worker -- ls -la /var/lib/ate/actors Control plane / snapshots (for the tag-scope subtest) kubectl ate get tags -a ate-e2e-sec-a # atespace-a-tag appears during the last subtest kubectl ate get atespaces # ate-e2e-sec-a / -b listed while live -kubectl logs -n ate-system -l app=ateapi -f # Create \ No newline at end of file +kubectl logs -n ate-system -l app=ate-api-server -f # Create \ No newline at end of file