mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Harden demo SSH setup for private deploy hosts
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run
Canonical Governance / governance (push) Waiting to run
Helm CI / Lint and Render Chart (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 1/4) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 2/4) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 3/4) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 4/4) (push) Waiting to run
Core E2E Tests / E2E verdict (push) Blocked by required conditions
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run
Canonical Governance / governance (push) Waiting to run
Helm CI / Lint and Render Chart (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 1/4) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 2/4) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 3/4) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 4/4) (push) Waiting to run
Core E2E Tests / E2E verdict (push) Blocked by required conditions
This commit is contained in:
parent
584aa10699
commit
76ced45c3a
5 changed files with 76 additions and 5 deletions
31
.github/workflows/deploy-demo-server.yml
vendored
31
.github/workflows/deploy-demo-server.yml
vendored
|
|
@ -149,8 +149,35 @@ jobs:
|
||||||
chmod 700 ~/.ssh
|
chmod 700 ~/.ssh
|
||||||
echo "$DEMO_SERVER_SSH_KEY" > ~/.ssh/id_ed25519
|
echo "$DEMO_SERVER_SSH_KEY" > ~/.ssh/id_ed25519
|
||||||
chmod 600 ~/.ssh/id_ed25519
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
ssh-keyscan -H "$DEMO_SERVER_HOST" >> ~/.ssh/known_hosts
|
|
||||||
chmod 600 ~/.ssh/known_hosts
|
: > ~/.ssh/known_hosts
|
||||||
|
keyscan_output="$(mktemp)"
|
||||||
|
keyscan_error="$(mktemp)"
|
||||||
|
MAX_SSH_SETUP_ATTEMPTS=18
|
||||||
|
for attempt in $(seq 1 "$MAX_SSH_SETUP_ATTEMPTS"); do
|
||||||
|
if getent hosts "$DEMO_SERVER_HOST" >/dev/null 2>&1; then
|
||||||
|
if ssh-keyscan -T 10 -H "$DEMO_SERVER_HOST" > "$keyscan_output" 2>"$keyscan_error" && [ -s "$keyscan_output" ]; then
|
||||||
|
cat "$keyscan_output" >> ~/.ssh/known_hosts
|
||||||
|
chmod 600 ~/.ssh/known_hosts
|
||||||
|
echo "Demo SSH host key captured."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "ssh-keyscan did not return demo host keys on attempt ${attempt}/${MAX_SSH_SETUP_ATTEMPTS}."
|
||||||
|
else
|
||||||
|
echo "Demo SSH host is not resolvable yet on attempt ${attempt}/${MAX_SSH_SETUP_ATTEMPTS}."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$attempt" -lt "$MAX_SSH_SETUP_ATTEMPTS" ]; then
|
||||||
|
sleep 10
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "::error::Timed out waiting for the demo SSH host to resolve and return host keys after Tailscale setup."
|
||||||
|
tailscale status --peers=false || true
|
||||||
|
if [ -s "$keyscan_error" ]; then
|
||||||
|
sed 's/^/ssh-keyscan: /' "$keyscan_error" || true
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
|
||||||
- name: Verify target host identity
|
- name: Verify target host identity
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
31
.github/workflows/update-demo-server.yml
vendored
31
.github/workflows/update-demo-server.yml
vendored
|
|
@ -256,8 +256,35 @@ jobs:
|
||||||
chmod 700 ~/.ssh
|
chmod 700 ~/.ssh
|
||||||
echo "$DEMO_SERVER_SSH_KEY" > ~/.ssh/id_ed25519
|
echo "$DEMO_SERVER_SSH_KEY" > ~/.ssh/id_ed25519
|
||||||
chmod 600 ~/.ssh/id_ed25519
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
ssh-keyscan -H "$DEMO_SERVER_HOST" >> ~/.ssh/known_hosts
|
|
||||||
chmod 600 ~/.ssh/known_hosts
|
: > ~/.ssh/known_hosts
|
||||||
|
keyscan_output="$(mktemp)"
|
||||||
|
keyscan_error="$(mktemp)"
|
||||||
|
MAX_SSH_SETUP_ATTEMPTS=18
|
||||||
|
for attempt in $(seq 1 "$MAX_SSH_SETUP_ATTEMPTS"); do
|
||||||
|
if getent hosts "$DEMO_SERVER_HOST" >/dev/null 2>&1; then
|
||||||
|
if ssh-keyscan -T 10 -H "$DEMO_SERVER_HOST" > "$keyscan_output" 2>"$keyscan_error" && [ -s "$keyscan_output" ]; then
|
||||||
|
cat "$keyscan_output" >> ~/.ssh/known_hosts
|
||||||
|
chmod 600 ~/.ssh/known_hosts
|
||||||
|
echo "Demo SSH host key captured."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "ssh-keyscan did not return demo host keys on attempt ${attempt}/${MAX_SSH_SETUP_ATTEMPTS}."
|
||||||
|
else
|
||||||
|
echo "Demo SSH host is not resolvable yet on attempt ${attempt}/${MAX_SSH_SETUP_ATTEMPTS}."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$attempt" -lt "$MAX_SSH_SETUP_ATTEMPTS" ]; then
|
||||||
|
sleep 10
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "::error::Timed out waiting for the demo SSH host to resolve and return host keys after Tailscale setup."
|
||||||
|
tailscale status --peers=false || true
|
||||||
|
if [ -s "$keyscan_error" ]; then
|
||||||
|
sed 's/^/ssh-keyscan: /' "$keyscan_error" || true
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
|
||||||
- name: Verify target host identity
|
- name: Verify target host identity
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
|
|
@ -1183,7 +1183,10 @@ Those same governed demo deploy/update workflows also own the runner-to-host
|
||||||
network path. They must establish the canonical Tailscale connectivity step
|
network path. They must establish the canonical Tailscale connectivity step
|
||||||
before SSH setup so stable or preview targets may stay on governed private
|
before SSH setup so stable or preview targets may stay on governed private
|
||||||
hostnames or Tailscale IPs, rather than silently depending on public SSH
|
hostnames or Tailscale IPs, rather than silently depending on public SSH
|
||||||
reachability from GitHub-hosted runners.
|
reachability from GitHub-hosted runners. After Tailscale setup, SSH setup must
|
||||||
|
wait for the configured demo host to resolve and return host keys with bounded
|
||||||
|
retries before any installer or binary copy runs; a one-shot `ssh-keyscan`
|
||||||
|
against a private demo hostname is not sufficient release or deploy proof.
|
||||||
Those same workflows also own customer-visible browser truth for the public
|
Those same workflows also own customer-visible browser truth for the public
|
||||||
demo shell. Health checks and entry-asset parity are necessary but not
|
demo shell. Health checks and entry-asset parity are necessary but not
|
||||||
sufficient; after those checks pass, the governed helpers
|
sufficient; after those checks pass, the governed helpers
|
||||||
|
|
|
||||||
|
|
@ -947,6 +947,10 @@ func TestDeployDemoWorkflowFailsClosedForStableAndVerifiesFrontendParity(t *test
|
||||||
` - stable`,
|
` - stable`,
|
||||||
`Capture expected frontend entry asset`,
|
`Capture expected frontend entry asset`,
|
||||||
`Verify target host identity`,
|
`Verify target host identity`,
|
||||||
|
`MAX_SSH_SETUP_ATTEMPTS=18`,
|
||||||
|
`getent hosts "$DEMO_SERVER_HOST"`,
|
||||||
|
`ssh-keyscan -T 10 -H "$DEMO_SERVER_HOST"`,
|
||||||
|
`Timed out waiting for the demo SSH host to resolve and return host keys after Tailscale setup.`,
|
||||||
`SERVICE_NAME="pulse"`,
|
`SERVICE_NAME="pulse"`,
|
||||||
`Unsupported demo target: ${TARGET}`,
|
`Unsupported demo target: ${TARGET}`,
|
||||||
`Demo environment points at host $REMOTE_HOSTNAME but expected $DEMO_EXPECTED_HOSTNAME.`,
|
`Demo environment points at host $REMOTE_HOSTNAME but expected $DEMO_EXPECTED_HOSTNAME.`,
|
||||||
|
|
@ -987,6 +991,10 @@ func TestUpdateDemoWorkflowUsesGovernedNetworkPath(t *testing.T) {
|
||||||
`go run ./scripts/release_update_key.go public-key-ssh`,
|
`go run ./scripts/release_update_key.go public-key-ssh`,
|
||||||
`sed -i "s|^PINNED_RELEASE_SSH_PUBLIC_KEY=.*|PINNED_RELEASE_SSH_PUBLIC_KEY=\"${TRUSTED_SSH_PUBLIC_KEY}\"|" /tmp/pulse-install.sh`,
|
`sed -i "s|^PINNED_RELEASE_SSH_PUBLIC_KEY=.*|PINNED_RELEASE_SSH_PUBLIC_KEY=\"${TRUSTED_SSH_PUBLIC_KEY}\"|" /tmp/pulse-install.sh`,
|
||||||
`Verify target host identity`,
|
`Verify target host identity`,
|
||||||
|
`MAX_SSH_SETUP_ATTEMPTS=18`,
|
||||||
|
`getent hosts "$DEMO_SERVER_HOST"`,
|
||||||
|
`ssh-keyscan -T 10 -H "$DEMO_SERVER_HOST"`,
|
||||||
|
`Timed out waiting for the demo SSH host to resolve and return host keys after Tailscale setup.`,
|
||||||
`Demo environment points at host $REMOTE_HOSTNAME but expected $DEMO_EXPECTED_HOSTNAME.`,
|
`Demo environment points at host $REMOTE_HOSTNAME but expected $DEMO_EXPECTED_HOSTNAME.`,
|
||||||
`Prepare demo host storage`,
|
`Prepare demo host storage`,
|
||||||
`KEEP_BACKUPS=2`,
|
`KEEP_BACKUPS=2`,
|
||||||
|
|
|
||||||
|
|
@ -525,6 +525,7 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
||||||
def test_release_workflow_enforces_rc_lineage_soak_and_v5_notice(self) -> None:
|
def test_release_workflow_enforces_rc_lineage_soak_and_v5_notice(self) -> None:
|
||||||
content = read(".github/workflows/create-release.yml")
|
content = read(".github/workflows/create-release.yml")
|
||||||
update_demo_workflow = read(".github/workflows/update-demo-server.yml")
|
update_demo_workflow = read(".github/workflows/update-demo-server.yml")
|
||||||
|
deploy_demo_workflow = read(".github/workflows/deploy-demo-server.yml")
|
||||||
validation_workflow = read(".github/workflows/validate-release-assets.yml")
|
validation_workflow = read(".github/workflows/validate-release-assets.yml")
|
||||||
helper = read("scripts/trigger-release.sh")
|
helper = read("scripts/trigger-release.sh")
|
||||||
renderer = read("scripts/release_control/render_release_body.py")
|
renderer = read("scripts/release_control/render_release_body.py")
|
||||||
|
|
@ -610,6 +611,11 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
||||||
self.assertIn("does not trust the configured release signing key", content)
|
self.assertIn("does not trust the configured release signing key", content)
|
||||||
self.assertIn("TRUSTED_SSH_PUBLIC_KEY", update_demo_workflow)
|
self.assertIn("TRUSTED_SSH_PUBLIC_KEY", update_demo_workflow)
|
||||||
self.assertIn('sed -i "s|^PINNED_RELEASE_SSH_PUBLIC_KEY=.*|PINNED_RELEASE_SSH_PUBLIC_KEY=\\"${TRUSTED_SSH_PUBLIC_KEY}\\"|" /tmp/pulse-install.sh', update_demo_workflow)
|
self.assertIn('sed -i "s|^PINNED_RELEASE_SSH_PUBLIC_KEY=.*|PINNED_RELEASE_SSH_PUBLIC_KEY=\\"${TRUSTED_SSH_PUBLIC_KEY}\\"|" /tmp/pulse-install.sh', update_demo_workflow)
|
||||||
|
for demo_workflow in (update_demo_workflow, deploy_demo_workflow):
|
||||||
|
self.assertIn("MAX_SSH_SETUP_ATTEMPTS=18", demo_workflow)
|
||||||
|
self.assertIn('getent hosts "$DEMO_SERVER_HOST"', demo_workflow)
|
||||||
|
self.assertIn('ssh-keyscan -T 10 -H "$DEMO_SERVER_HOST"', demo_workflow)
|
||||||
|
self.assertIn("Timed out waiting for the demo SSH host to resolve and return host keys after Tailscale setup", demo_workflow)
|
||||||
self.assertIn("derive the OpenSSH installer trust key from `PULSE_UPDATE_SIGNING_PUBLIC_KEY`", normalize_ws(contract))
|
self.assertIn("derive the OpenSSH installer trust key from `PULSE_UPDATE_SIGNING_PUBLIC_KEY`", normalize_ws(contract))
|
||||||
self.assertIn('SYFT_VERSION="1.42.4"', content)
|
self.assertIn('SYFT_VERSION="1.42.4"', content)
|
||||||
self.assertIn('SYFT_ARCHIVE="syft_${SYFT_VERSION}_linux_amd64.tar.gz"', content)
|
self.assertIn('SYFT_ARCHIVE="syft_${SYFT_VERSION}_linux_amd64.tar.gz"', content)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue