From bc8c35896f049f6c6bfc75ba5751df64d0a0cd50 Mon Sep 17 00:00:00 2001 From: bluejay Date: Tue, 26 May 2026 03:42:01 +0000 Subject: [PATCH] tests: add bluejay-ws runner-exclusion lint + fix 3 stale runner-fleet assertions (#30) BLUEJAY-WS must never be a fleet GHA runner (operator directive 2026-05-26). Build-side analog of Sprint 9 safe-account exclusion. Also fixes 3 stale runner-fleet assertions broken by initContainer addition + replica tuning. --- .../FleetManifestLintTests.cs | 127 +++++++++++++++++- 1 file changed, 124 insertions(+), 3 deletions(-) diff --git a/tests/bluejay-infra-lint/FleetManifestLintTests.cs b/tests/bluejay-infra-lint/FleetManifestLintTests.cs index e7ad012..dfdc671 100644 --- a/tests/bluejay-infra-lint/FleetManifestLintTests.cs +++ b/tests/bluejay-infra-lint/FleetManifestLintTests.cs @@ -236,7 +236,7 @@ public sealed class FleetManifestLintTests { deployments.Should().ContainKey(expectedRunner.Key); - var container = deployments[expectedRunner.Key].ContainerMappings().Should().ContainSingle().Subject; + var container = deployments[expectedRunner.Key].MainContainerMappings().Should().ContainSingle().Subject; EnvValue(container, "REPO_URL").Should().Be(expectedRunner.Value); EnvValue(container, "EPHEMERAL").Should().Be("true"); EnvValue(container, "LABELS").Should().Be("self-hosted,linux,fc-build-linux"); @@ -252,7 +252,7 @@ public sealed class FleetManifestLintTests { foreach (var deployment in GitHubRunnerDeployments().Values) { - var container = deployment.ContainerMappings().Should().ContainSingle().Subject; + var container = deployment.MainContainerMappings().Should().ContainSingle().Subject; foreach (var expectedEnv in WritableRunnerEnv) { @@ -279,7 +279,10 @@ public sealed class FleetManifestLintTests foreach (var deploymentName in ScaledLinuxRunnerDeployments) { var deployment = deployments[deploymentName]; - ReplicaCount(deployment).Should().Be(2); + // Scaled runners must have >= 2 replicas (avoid single-pod bottleneck). + // Individual deployments may be tuned upward per CI activity — see + // "runners: right-size replica counts per 14d CI activity (#24)". + ReplicaCount(deployment).Should().BeGreaterOrEqualTo(2, $"{deploymentName} is in the scaled set and must run with at least 2 replicas"); var volumes = deployment.MappingSequence("spec", "template", "spec", "volumes"); var claimNames = volumes @@ -305,6 +308,108 @@ public sealed class FleetManifestLintTests .Be("github-runner-nuget-cache"); } + [Fact] + public void Runners_MustNotPinToOperatorWorkstationHosts() + { + // CRITICAL SAFETY (operator directive 2026-05-26): BLUEJAY-WS is the + // operator's primary workstation — host of the 1Password Connect + // bearer token, fcadmin SSH keys to noc1, signing CA private keys, + // and source for every FC repo. A self-hosted GitHub Actions runner + // there would execute arbitrary PR code with that local access. + // Build-side analog of the Sprint 9 NEW safe-account exclusion gate + // (Puppet GPO/AppLocker/WDAC/audit-forwarder modules refuse to apply + // on BLUEJAY-WS). This lint asserts no GitHub-runner Deployment in + // apps/github-runner/ pins to a forbidden operator-workstation host + // via nodeName, nodeSelector, nodeAffinity, or tolerations. + // Existing legacy `bluejay-ws-sandbox-1` GitHub-registered runner is + // out of scope here (it's a runtime registration, not a K8s + // Deployment) — see CLAUDE.md "Common Mistakes" entry and + // feedback_bluejay_ws_never_public_runner.md. + var forbiddenHostPatterns = new[] + { + "bluejay-ws", + "BLUEJAY-WS", + "bluejay-ws.iamworkin.lan", + "iamworkin-ws", + }; + + bool ContainsForbidden(string? value) => + !string.IsNullOrWhiteSpace(value) + && forbiddenHostPatterns.Any(pattern => value!.Contains(pattern, StringComparison.OrdinalIgnoreCase)); + + var violations = GitHubRunnerDeployments().Values.SelectMany(deployment => + { + var local = new List(); + var podSpec = ManifestNodeExtensions.Mapping(deployment.Root, "spec", "template", "spec"); + if (podSpec is null) + { + return local; + } + + // nodeName: pins the pod to a specific node by name. + var nodeName = ManifestNodeExtensions.Scalar(podSpec, "nodeName"); + if (ContainsForbidden(nodeName)) + { + local.Add($"{deployment.Name} sets nodeName='{nodeName}' which targets a forbidden operator-workstation host."); + } + + // nodeSelector: dict of label → value pinning the pod to nodes + // carrying matching labels. Examples that would trip this: + // kubernetes.io/hostname: bluejay-ws + // flowercore.io/host: bluejay-ws.iamworkin.lan + var nodeSelector = ManifestNodeExtensions.Mapping(podSpec, "nodeSelector"); + if (nodeSelector is not null) + { + foreach (var entry in nodeSelector.Children) + { + var key = entry.Key is YamlScalarNode keyScalar ? keyScalar.Value : null; + var value = entry.Value is YamlScalarNode valueScalar ? valueScalar.Value : null; + if (ContainsForbidden(value)) + { + local.Add($"{deployment.Name} has nodeSelector entry '{key}: {value}' which targets a forbidden operator-workstation host."); + } + } + } + + // nodeAffinity: matchExpressions over node labels. + foreach (var term in ManifestNodeExtensions.MappingSequence(podSpec, "affinity", "nodeAffinity", "requiredDuringSchedulingIgnoredDuringExecution", "nodeSelectorTerms")) + { + foreach (var expr in ManifestNodeExtensions.MappingSequence(term, "matchExpressions")) + { + var key = ManifestNodeExtensions.Scalar(expr, "key"); + foreach (var valueNode in ManifestNodeExtensions.ScalarSequence(expr, "values")) + { + if (ContainsForbidden(valueNode)) + { + local.Add($"{deployment.Name} has nodeAffinity matchExpression '{key}' value '{valueNode}' which targets a forbidden operator-workstation host."); + } + } + } + } + + // tolerations: scheduling onto a tainted operator-workstation + // node would let the runner run there. Forbid any toleration + // value that names the workstation. + foreach (var toleration in ManifestNodeExtensions.MappingSequence(podSpec, "tolerations")) + { + var key = ManifestNodeExtensions.Scalar(toleration, "key"); + var value = ManifestNodeExtensions.Scalar(toleration, "value"); + if (ContainsForbidden(key)) + { + local.Add($"{deployment.Name} has toleration key '{key}' which targets a forbidden operator-workstation host."); + } + if (ContainsForbidden(value)) + { + local.Add($"{deployment.Name} has toleration value '{value}' which targets a forbidden operator-workstation host."); + } + } + + return local; + }).ToList(); + + violations.Should().BeEmpty("BLUEJAY-WS / iamworkin-ws must never host a fleet GitHub Actions runner; see CLAUDE.md 'Registering BLUEJAY-WS as a fleet GitHub Actions runner' and feedback_bluejay_ws_never_public_runner.md"); + } + [Fact] public void Monitoring_MustAlertWhenLinuxRunnerDeploymentIsUnavailable() { @@ -892,6 +997,22 @@ internal sealed record ManifestDocument( .ToList(); } + // MainContainerMappings excludes initContainers. Use this when asserting + // properties of the primary container (env, image, volumeMounts) where an + // initContainer would be a false-positive match — e.g. the GitHub runner + // image's `setup-runner-home` initContainer should not count toward the + // single-container assertions on the runner deployments. + public IReadOnlyList MainContainerMappings() + { + var podSpec = PodSpec(); + if (podSpec is null) + { + return Array.Empty(); + } + + return ManifestNodeExtensions.MappingSequence(podSpec, "containers").ToList(); + } + public IReadOnlyList ContainerSpecs() { return ContainerMappings()