From ee14d3a2d018ffaa859de81020abc4cbf18cfe33 Mon Sep 17 00:00:00 2001 From: Andrew Stoltz <1578013+astoltz@users.noreply.github.com> Date: Wed, 17 Jun 2026 19:54:26 -0500 Subject: [PATCH] whc4: front bluejay tenant route with CRS WAF --- .../deployment-andrew-web-waf.json | 180 ++++++++++++++++++ .../ingressroute-andrew-web.json | 16 +- .../service-andrew-web-waf.json | 24 +++ .../FleetManifestLintTests.cs | 49 +++++ 4 files changed, 261 insertions(+), 8 deletions(-) create mode 100644 apps-gx10/fc-tenant-andrew/deployment-andrew-web-waf.json create mode 100644 apps-gx10/fc-tenant-andrew/service-andrew-web-waf.json diff --git a/apps-gx10/fc-tenant-andrew/deployment-andrew-web-waf.json b/apps-gx10/fc-tenant-andrew/deployment-andrew-web-waf.json new file mode 100644 index 0000000..6cd81c6 --- /dev/null +++ b/apps-gx10/fc-tenant-andrew/deployment-andrew-web-waf.json @@ -0,0 +1,180 @@ +{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "labels": { + "app.kubernetes.io/managed-by": "flowercore", + "app.kubernetes.io/name": "andrew-web-waf" + }, + "name": "andrew-web-waf", + "namespace": "fc-tenant-andrew" + }, + "spec": { + "progressDeadlineSeconds": 600, + "replicas": 1, + "revisionHistoryLimit": 10, + "selector": { + "matchLabels": { + "app.kubernetes.io/name": "andrew-web-waf" + } + }, + "strategy": { + "type": "Recreate" + }, + "template": { + "metadata": { + "labels": { + "app.kubernetes.io/name": "andrew-web-waf" + } + }, + "spec": { + "containers": [ + { + "env": [ + { + "name": "BACKEND", + "value": "http://andrew-web.fc-tenant-andrew.svc.cluster.local:80" + }, + { + "name": "SERVER_NAME", + "value": "bluejay.dev www.bluejay.dev" + }, + { + "name": "PORT", + "value": "8080" + }, + { + "name": "PROXY_PRESERVE_HOST", + "value": "on" + }, + { + "name": "PROXY_TIMEOUT", + "value": "60s" + }, + { + "name": "MODSEC_RULE_ENGINE", + "value": "On" + }, + { + "name": "MODSEC_AUDIT_ENGINE", + "value": "RelevantOnly" + }, + { + "name": "MODSEC_AUDIT_LOG", + "value": "/dev/stdout" + }, + { + "name": "MODSEC_AUDIT_LOG_TYPE", + "value": "Serial" + }, + { + "name": "LOGLEVEL", + "value": "warn" + }, + { + "name": "ERRORLOG", + "value": "/dev/stderr" + }, + { + "name": "ACCESSLOG", + "value": "/dev/stdout" + }, + { + "name": "BLOCKING_PARANOIA", + "value": "1" + }, + { + "name": "DETECTION_PARANOIA", + "value": "1" + }, + { + "name": "ANOMALY_INBOUND", + "value": "5" + }, + { + "name": "ANOMALY_OUTBOUND", + "value": "4" + } + ], + "image": "owasp/modsecurity-crs:4.25-nginx-alpine-lts@sha256:88b59911549723e71beabf3b4aa47bbd31b00e79401f442e65ddfc430ae46343", + "imagePullPolicy": "IfNotPresent", + "livenessProbe": { + "failureThreshold": 3, + "httpGet": { + "httpHeaders": [ + { + "name": "Host", + "value": "bluejay.dev" + } + ], + "path": "/healthz", + "port": 8080, + "scheme": "HTTP" + }, + "initialDelaySeconds": 20, + "periodSeconds": 30, + "successThreshold": 1, + "timeoutSeconds": 2 + }, + "name": "andrew-web-waf", + "ports": [ + { + "containerPort": 8080, + "name": "http", + "protocol": "TCP" + } + ], + "readinessProbe": { + "failureThreshold": 3, + "httpGet": { + "httpHeaders": [ + { + "name": "Host", + "value": "bluejay.dev" + } + ], + "path": "/healthz", + "port": 8080, + "scheme": "HTTP" + }, + "initialDelaySeconds": 10, + "periodSeconds": 10, + "successThreshold": 1, + "timeoutSeconds": 2 + }, + "resources": { + "limits": { + "cpu": "500m", + "memory": "512Mi" + }, + "requests": { + "cpu": "100m", + "memory": "128Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + } + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File" + } + ], + "enableServiceLinks": false, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": { + "fsGroup": 101, + "runAsGroup": 101, + "runAsNonRoot": true, + "runAsUser": 101 + }, + "terminationGracePeriodSeconds": 30 + } + } + } +} diff --git a/apps-gx10/fc-tenant-andrew/ingressroute-andrew-web.json b/apps-gx10/fc-tenant-andrew/ingressroute-andrew-web.json index bb58190..a508e87 100644 --- a/apps-gx10/fc-tenant-andrew/ingressroute-andrew-web.json +++ b/apps-gx10/fc-tenant-andrew/ingressroute-andrew-web.json @@ -13,14 +13,14 @@ { "kind": "Rule", "match": "Host(`bluejay.dev`) || Host(`www.bluejay.dev`)", - "priority": 100, - "services": [ - { - "name": "andrew-web", - "port": 80 - } - ] - } + "priority": 100, + "services": [ + { + "name": "andrew-web-waf", + "port": 8080 + } + ] + } ], "tls": { "secretName": "cf-origin-bluejay-dev" diff --git a/apps-gx10/fc-tenant-andrew/service-andrew-web-waf.json b/apps-gx10/fc-tenant-andrew/service-andrew-web-waf.json new file mode 100644 index 0000000..e5f39d3 --- /dev/null +++ b/apps-gx10/fc-tenant-andrew/service-andrew-web-waf.json @@ -0,0 +1,24 @@ +{ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "andrew-web-waf", + "namespace": "fc-tenant-andrew" + }, + "spec": { + "internalTrafficPolicy": "Cluster", + "ports": [ + { + "name": "http", + "port": 8080, + "protocol": "TCP", + "targetPort": 8080 + } + ], + "selector": { + "app.kubernetes.io/name": "andrew-web-waf" + }, + "sessionAffinity": "None", + "type": "ClusterIP" + } +} diff --git a/tests/bluejay-infra-lint/FleetManifestLintTests.cs b/tests/bluejay-infra-lint/FleetManifestLintTests.cs index e687b7a..3135a41 100644 --- a/tests/bluejay-infra-lint/FleetManifestLintTests.cs +++ b/tests/bluejay-infra-lint/FleetManifestLintTests.cs @@ -1071,6 +1071,55 @@ public sealed class FleetManifestLintTests serviceRef.GetProperty("port").GetInt32().Should().Be(8080); } + [Fact] + public void Gx10BluejayDevTenantRoute_IsFrontedByOwaspCrsWaf() + { + var appRoot = Path.Combine(Inventory.BluejayRoot, "apps-gx10", "fc-tenant-andrew"); + var wafContainer = Gx10DeploymentContainer("fc-tenant-andrew", "deployment-andrew-web-waf.json"); + wafContainer.GetProperty("image").GetString() + .Should() + .Be("owasp/modsecurity-crs:4.25-nginx-alpine-lts@sha256:88b59911549723e71beabf3b4aa47bbd31b00e79401f442e65ddfc430ae46343"); + JsonEnvValue(wafContainer, "BACKEND").Should().Be("http://andrew-web.fc-tenant-andrew.svc.cluster.local:80"); + JsonEnvValue(wafContainer, "SERVER_NAME").Should().Be("bluejay.dev www.bluejay.dev"); + JsonEnvValue(wafContainer, "MODSEC_RULE_ENGINE").Should().Be("On"); + JsonEnvValue(wafContainer, "MODSEC_AUDIT_ENGINE").Should().Be("RelevantOnly"); + JsonEnvValue(wafContainer, "MODSEC_AUDIT_LOG").Should().Be("/dev/stdout"); + + using var wafDeployment = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "deployment-andrew-web-waf.json"))); + var podSpec = wafDeployment.RootElement + .GetProperty("spec") + .GetProperty("template") + .GetProperty("spec"); + podSpec.GetProperty("enableServiceLinks").GetBoolean().Should().BeFalse(); + podSpec.GetProperty("securityContext").GetProperty("runAsUser").GetInt32().Should().Be(101); + podSpec.GetProperty("securityContext").GetProperty("runAsNonRoot").GetBoolean().Should().BeTrue(); + wafContainer.GetProperty("readinessProbe") + .GetProperty("httpGet") + .GetProperty("httpHeaders")[0] + .GetProperty("value") + .GetString() + .Should() + .Be("bluejay.dev"); + + using var service = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "service-andrew-web-waf.json"))); + service.RootElement.GetProperty("spec").GetProperty("selector").GetProperty("app.kubernetes.io/name").GetString().Should().Be("andrew-web-waf"); + var servicePort = service.RootElement.GetProperty("spec").GetProperty("ports").EnumerateArray().Should().ContainSingle().Subject; + servicePort.GetProperty("port").GetInt32().Should().Be(8080); + servicePort.GetProperty("targetPort").GetInt32().Should().Be(8080); + + using var ingressRoute = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "ingressroute-andrew-web.json"))); + var serviceRef = ingressRoute.RootElement + .GetProperty("spec") + .GetProperty("routes")[0] + .GetProperty("services") + .EnumerateArray() + .Should() + .ContainSingle() + .Subject; + serviceRef.GetProperty("name").GetString().Should().Be("andrew-web-waf"); + serviceRef.GetProperty("port").GetInt32().Should().Be(8080); + } + [Fact] public void Gx10HostingManagers_ProvisioningCrdsAndRbacMustBeGitOpsOwned() {