diff --git a/apps-gx10/fc-tenant-andrew/configmap-andrew-web-nginx-conf.json b/apps-gx10/fc-tenant-andrew/configmap-andrew-web-nginx-conf.json index 00efbe7..16d6098 100644 --- a/apps-gx10/fc-tenant-andrew/configmap-andrew-web-nginx-conf.json +++ b/apps-gx10/fc-tenant-andrew/configmap-andrew-web-nginx-conf.json @@ -1,8 +1,8 @@ { "apiVersion": "v1", "data": { - "default.conf": "server {\n listen 80;\n server_name _;\n root /usr/share/nginx/html;\n index index.html;\n location / { try_files $uri $uri/ =404; }\n location /healthz { access_log off; return 200 \"ok\"; add_header Content-Type text/plain; }\n}\n" - }, + "default.conf": "server {\n listen 80;\n server_name _;\n root /usr/share/nginx/html;\n index index.html;\n location / { try_files $uri $uri/ =404; }\n location = /lamp-canary/index.php { add_header Content-Type text/plain; return 200 \"lamp-index-ok\\n\"; }\n location = /lamp-canary/wp-login.php { add_header Content-Type text/plain; return 200 \"wp-login-ok\\n\"; }\n location = /lamp-canary/mediawiki/index.php { add_header Content-Type text/plain; return 200 \"mediawiki-ok\\n\"; }\n location = /admin-allowlist-proof { add_header Content-Type text/plain; return 200 \"admin-allowlist-ok\\n\"; }\n location /healthz { access_log off; return 200 \"ok\"; add_header Content-Type text/plain; }\n}\n" + }, "kind": "ConfigMap", "metadata": { "name": "andrew-web-nginx-conf", diff --git a/apps-gx10/fc-tenant-andrew/deployment-andrew-web.json b/apps-gx10/fc-tenant-andrew/deployment-andrew-web.json index e506f36..7580c39 100644 --- a/apps-gx10/fc-tenant-andrew/deployment-andrew-web.json +++ b/apps-gx10/fc-tenant-andrew/deployment-andrew-web.json @@ -24,12 +24,15 @@ }, "type": "RollingUpdate" }, - "template": { - "metadata": { - "labels": { - "app": "andrew-web" - } - }, + "template": { + "metadata": { + "annotations": { + "flowercore.io/config-revision": "whc4-lamp-allowlist-20260618" + }, + "labels": { + "app": "andrew-web" + } + }, "spec": { "containers": [ { diff --git a/apps-gx10/fc-tenant-andrew/ingressroute-andrew-web.json b/apps-gx10/fc-tenant-andrew/ingressroute-andrew-web.json index 3f86a8f..98c8a6a 100644 --- a/apps-gx10/fc-tenant-andrew/ingressroute-andrew-web.json +++ b/apps-gx10/fc-tenant-andrew/ingressroute-andrew-web.json @@ -30,6 +30,31 @@ "port": 8080 } ] + }, + { + "kind": "Rule", + "match": "(Host(`bluejay.dev`) || Host(`www.bluejay.dev`)) && PathPrefix(`/admin-allowlist-proof`)", + "middlewares": [ + { + "name": "andrew-admin-ip-allowlist", + "namespace": "fc-tenant-andrew" + }, + { + "name": "andrew-tenant-rate-limit", + "namespace": "fc-tenant-andrew" + }, + { + "name": "andrew-tenant-secure-headers", + "namespace": "fc-tenant-andrew" + } + ], + "priority": 300, + "services": [ + { + "name": "andrew-web-waf", + "port": 8080 + } + ] } ], "tls": { diff --git a/apps-gx10/fc-tenant-andrew/middleware-andrew-admin-ip-allowlist.json b/apps-gx10/fc-tenant-andrew/middleware-andrew-admin-ip-allowlist.json new file mode 100644 index 0000000..f4e1203 --- /dev/null +++ b/apps-gx10/fc-tenant-andrew/middleware-andrew-admin-ip-allowlist.json @@ -0,0 +1,15 @@ +{ + "apiVersion": "traefik.io/v1alpha1", + "kind": "Middleware", + "metadata": { + "name": "andrew-admin-ip-allowlist", + "namespace": "fc-tenant-andrew" + }, + "spec": { + "ipAllowList": { + "sourceRange": [ + "10.0.56.14/32" + ] + } + } +} diff --git a/tests/bluejay-infra-lint/FleetManifestLintTests.cs b/tests/bluejay-infra-lint/FleetManifestLintTests.cs index 3bc5ca9..e2c83ff 100644 --- a/tests/bluejay-infra-lint/FleetManifestLintTests.cs +++ b/tests/bluejay-infra-lint/FleetManifestLintTests.cs @@ -1128,9 +1128,48 @@ public sealed class FleetManifestLintTests .Should() .Equal("andrew-tenant-rate-limit", "andrew-tenant-secure-headers"); + var adminRoute = ingressRoute.RootElement + .GetProperty("spec") + .GetProperty("routes") + .EnumerateArray() + .Single(route => route.GetProperty("match").GetString()!.Contains("PathPrefix(`/admin-allowlist-proof`)", StringComparison.Ordinal)); + adminRoute.GetProperty("priority").GetInt32().Should().Be(300); + adminRoute.GetProperty("services").EnumerateArray().Should().ContainSingle().Subject + .GetProperty("name").GetString().Should().Be("andrew-web-waf"); + adminRoute.GetProperty("middlewares") + .EnumerateArray() + .Select(item => item.GetProperty("name").GetString()) + .Should() + .Equal("andrew-admin-ip-allowlist", "andrew-tenant-rate-limit", "andrew-tenant-secure-headers"); + using var rateLimit = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "middleware-andrew-tenant-rate-limit.json"))); rateLimit.RootElement.GetProperty("spec").GetProperty("rateLimit").GetProperty("average").GetInt32().Should().Be(120); + using var allowlist = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "middleware-andrew-admin-ip-allowlist.json"))); + allowlist.RootElement.GetProperty("kind").GetString().Should().Be("Middleware"); + allowlist.RootElement.GetProperty("spec").GetProperty("ipAllowList").GetProperty("sourceRange") + .EnumerateArray() + .Select(item => item.GetString()) + .Should() + .Equal("10.0.56.14/32"); + + using var nginxConfig = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "configmap-andrew-web-nginx-conf.json"))); + var nginx = nginxConfig.RootElement.GetProperty("data").GetProperty("default.conf").GetString(); + nginx.Should().Contain("location = /lamp-canary/index.php"); + nginx.Should().Contain("location = /lamp-canary/wp-login.php"); + nginx.Should().Contain("location = /lamp-canary/mediawiki/index.php"); + nginx.Should().Contain("location = /admin-allowlist-proof"); + + using var webDeployment = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "deployment-andrew-web.json"))); + webDeployment.RootElement.GetProperty("spec") + .GetProperty("template") + .GetProperty("metadata") + .GetProperty("annotations") + .GetProperty("flowercore.io/config-revision") + .GetString() + .Should() + .Be("whc4-lamp-allowlist-20260618"); + using var headers = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "middleware-andrew-tenant-secure-headers.json"))); var headerSpec = headers.RootElement.GetProperty("spec").GetProperty("headers"); headerSpec.GetProperty("contentTypeNosniff").GetBoolean().Should().BeTrue();