mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 22:14:25 +01:00
train T1: merge #3616 at cbc09f6699
This commit is contained in:
commit
a06708c43a
3 files changed
+94
No files matched your search
@@ -288,6 +288,21 @@ jobs:
|
||||
installed="${chrome%/*}/chrome-sandbox"
|
||||
test -f "$helper" && test ! -L "$helper"
|
||||
sudo install -T -o root -g root -m 4755 "$helper" "$installed"
|
||||
# Runner image 20261026.1.0 keeps kernel.apparmor_restrict_unprivileged_userns=1
|
||||
# but loads no AppArmor profiles, so Chromium's namespace sandbox aborts with
|
||||
# "credentials.cc: Permission denied". Grant user namespaces to this one
|
||||
# resolved binary, the way Ubuntu's own chrome profile does.
|
||||
- name: Let the bundled Chromium create its sandbox namespaces
|
||||
run: |
|
||||
set -euo pipefail
|
||||
chrome=$(bun -e 'import { chromium } from "playwright"; import { realpathSync } from "node:fs"; console.log(realpathSync(chromium.executablePath()))')
|
||||
case "$chrome" in
|
||||
"$HOME"/.cache/ms-playwright/chromium-*/chrome-linux*/chrome) ;;
|
||||
*) echo "Unexpected Chromium installation path" >&2; exit 1 ;;
|
||||
esac
|
||||
profile=/etc/apparmor.d/gstack-playwright-chromium
|
||||
printf 'abi <abi/4.0>,\ninclude <tunables/global>\nprofile gstack-playwright-chromium %s flags=(unconfined) {\n userns,\n}\n' "$chrome" | sudo tee "$profile" > /dev/null
|
||||
sudo apparmor_parser -r "$profile"
|
||||
- name: Install Xvfb + X11 utilities + gate tools
|
||||
run: sudo apt-get install -y --no-install-recommends xvfb x11-utils poppler-utils fonts-noto-color-emoji zsh
|
||||
- name: Configure git identity (tests init temp repos)
|
||||
@@ -397,6 +412,21 @@ jobs:
|
||||
sudo install -T -o root -g root -m 4755 "$helper" "$installed"
|
||||
test "$(stat -c '%u:%a' "$installed")" = '0:4755'
|
||||
cmp -s "$helper" "$installed"
|
||||
# Runner image 20261026.1.0 keeps kernel.apparmor_restrict_unprivileged_userns=1
|
||||
# but loads no AppArmor profiles, so Chromium's namespace sandbox aborts with
|
||||
# "credentials.cc: Permission denied". Grant user namespaces to this one
|
||||
# resolved binary, the way Ubuntu's own chrome profile does.
|
||||
- name: Let the bundled Chromium create its sandbox namespaces
|
||||
run: |
|
||||
set -euo pipefail
|
||||
chrome=$(bun -e 'import { chromium } from "playwright"; import { realpathSync } from "node:fs"; console.log(realpathSync(chromium.executablePath()))')
|
||||
case "$chrome" in
|
||||
"$HOME"/.cache/ms-playwright/chromium-*/chrome-linux*/chrome) ;;
|
||||
*) echo "Unexpected Chromium installation path" >&2; exit 1 ;;
|
||||
esac
|
||||
profile=/etc/apparmor.d/gstack-playwright-chromium
|
||||
printf 'abi <abi/4.0>,\ninclude <tunables/global>\nprofile gstack-playwright-chromium %s flags=(unconfined) {\n userns,\n}\n' "$chrome" | sudo tee "$profile" > /dev/null
|
||||
sudo apparmor_parser -r "$profile"
|
||||
|
||||
# Headed-browser tests (handoff, extension sidepanel DOM) need a real
|
||||
# DISPLAY — first Linux run failed with Playwright's "launched a headed
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
**Free Tests passes again on Ubicloud runner image 20261026.1.0.** Every Linux job that landed on that image failed the cookie-import Playwright pipe smoke, in-shard and again on the serial retry, and a rerun passed only when it happened to land on the older 20261021.1.0 image. Both images keep `kernel.apparmor_restrict_unprivileged_userns=1`, but 20261026 loads three AppArmor profiles instead of 123, so nothing grants the bundled Chromium a user namespace and its sandbox aborts with `credentials.cc:131 ... Permission denied`. The two Linux jobs that launch Chromium now grant user namespaces to that one binary, the way Ubuntu's own `chrome` profile does.
|
||||
|
||||
### Fixed
|
||||
- **`.github/workflows/free-tests.yml`**: `free-suite` and `quick-bun-default` gain a step after the SUID sandbox helper step. It resolves Playwright's Chromium, refuses any path outside `~/.cache/ms-playwright/chromium-*/chrome-linux*/chrome`, and loads an AppArmor profile that grants `userns` to that path alone. The kernel restriction stays on for every other binary. The SUID helper step is unchanged, and no step passes `--no-sandbox` or runs `sysctl`. Diagnostic dispatch 40921806393 ran 17 jobs: all 9 on 20261026 failed the smoke without the profile and passed with it, and all 8 on 20261021 passed throughout.
|
||||
|
||||
### Tests
|
||||
- `test/chromium-sandbox-ci.test.ts`: four cases, two per job, run the real step with stubbed `bun` and `sudo`. They check that the step sits after the helper and before the suite, the exact profile bytes and the `apparmor_parser -r` call, that no protection-lowering flag appears, and that an unexpected Chromium path is refused before any privileged call. They fail against main's workflow.
|
||||
|
||||
## [1.91.320.0] - 2026-10-29
|
||||
|
||||
**A branch that adds its own pregate lane now learns why it still fails, and `gstack-post pr-body` no longer keeps a stale copy of a section.** Pregate reads its policy from the base branch so a branch cannot loosen its own gate. A branch that had already declared a lane in its own `.gstack/pregate.json` was still told to declare it, and went round in a circle. Separately, a PR body that held `## What changed` twice (an old description pasted under the /ship body) kept the old copy every time /ship reposted.
|
||||
|
||||
@@ -75,3 +75,57 @@ test('the actual CI setup requires root ownership, setuid mode, and unchanged he
|
||||
expect(calls).not.toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
const NAMESPACE_STEP = 'Let the bundled Chromium create its sandbox namespaces';
|
||||
const PROFILE = '/etc/apparmor.d/gstack-playwright-chromium';
|
||||
|
||||
function grantNamespaces(command: string, options: { outside?: boolean } = {}) {
|
||||
const fixture = mkdtempSync(path.join(tmpdir(), 'chromium-userns-'));
|
||||
fixtures.push(fixture);
|
||||
const bin = path.join(fixture, 'bin');
|
||||
const home = path.join(fixture, 'home');
|
||||
const directory = options.outside ? path.join(fixture, 'outside') : path.join(home, '.cache/ms-playwright/chromium-1234/chrome-linux64');
|
||||
mkdirSync(bin, { recursive: true });
|
||||
mkdirSync(directory, { recursive: true });
|
||||
const executable = path.join(directory, 'chrome');
|
||||
const receipt = path.join(fixture, 'sudo.jsonl');
|
||||
writeFileSync(executable, 'synthetic browser');
|
||||
writeFileSync(path.join(bin, 'bun'), '#!/bin/sh\nprintf "%s\\n" "$FIXTURE_CHROME"\n', { mode: 0o755 });
|
||||
writeFileSync(path.join(bin, 'sudo'), `#!/usr/bin/env node
|
||||
const fs = require('node:fs');
|
||||
const args = process.argv.slice(2);
|
||||
const stdin = args[0] === 'tee' ? fs.readFileSync(0, 'utf8') : null;
|
||||
fs.appendFileSync(process.env.FIXTURE_RECEIPT, JSON.stringify({ args, stdin }) + '\\n');
|
||||
`, { mode: 0o755 });
|
||||
const result = spawnSync('/bin/bash', ['-c', command], {
|
||||
cwd: root, encoding: 'utf8', timeout: 10_000,
|
||||
env: { ...process.env, HOME: home, PATH: bin + path.delimiter + process.env.PATH, FIXTURE_CHROME: executable, FIXTURE_RECEIPT: receipt },
|
||||
});
|
||||
const calls = existsSync(receipt) ? readFileSync(receipt, 'utf8').trim().split('\n').map(line => JSON.parse(line)) : [];
|
||||
return { result, executable, calls };
|
||||
}
|
||||
|
||||
for (const [job, suiteStep] of [['free-suite', 'Run free suite'], ['quick-bun-default', 'Run the quick free tier on the install-default Bun']]) {
|
||||
const jobSteps = workflow.jobs[job].steps;
|
||||
const at = jobSteps.findIndex((step: any) => step.name === NAMESPACE_STEP);
|
||||
const grant = jobSteps[at]?.run;
|
||||
|
||||
test(`${job} grants user namespaces to the resolved bundled Chromium alone, after its helper and before its tests`, () => {
|
||||
expect(typeof grant).toBe('string');
|
||||
expect(at).toBeGreaterThan(jobSteps.findIndex((step: any) => step.name === 'Configure the bundled Chromium sandbox helper'));
|
||||
expect(at).toBeLessThan(jobSteps.findIndex((step: any) => step.name === suiteStep));
|
||||
expect(grant).not.toMatch(/--no-sandbox|chromiumSandbox:\s*false|sysctl|apparmor_restrict/);
|
||||
const { result, executable, calls } = grantNamespaces(grant);
|
||||
expect(result.status).toBe(0);
|
||||
expect(calls).toEqual([
|
||||
{ args: ['tee', PROFILE], stdin: `abi <abi/4.0>,\ninclude <tunables/global>\nprofile gstack-playwright-chromium ${executable} flags=(unconfined) {\n userns,\n}\n` },
|
||||
{ args: ['apparmor_parser', '-r', PROFILE], stdin: null },
|
||||
]);
|
||||
});
|
||||
|
||||
test(`${job} refuses to grant namespaces to an unexpected Chromium path`, () => {
|
||||
const { result, calls } = grantNamespaces(grant, { outside: true });
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(calls).toEqual([]);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user