Compare commits

..

2 Commits

Author SHA1 Message Date
cb4ea13e7a monitoring: mirror Sprint 60 probe coverage
Merged on local lint plus live noc1 Prometheus /api/v1/rules proof.
2026-06-04 18:19:47 +00:00
Andrew Stoltz
a3cd67d6bb monitoring: mirror Sprint 60 probe coverage 2026-06-04 13:15:18 -05:00
16 changed files with 39 additions and 196 deletions

View File

@@ -201,8 +201,6 @@ spec:
metadata: metadata:
labels: labels:
app: andrew-web app: andrew-web
annotations:
flowercore.io/healthz-auth-policy: "allow-anonymous"
spec: spec:
containers: containers:
- name: nginx - name: nginx
@@ -227,18 +225,12 @@ spec:
httpGet: httpGet:
path: /healthz path: /healthz
port: 80 port: 80
httpHeaders:
- name: X-Forwarded-Proto
value: https
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: 80 port: 80
httpHeaders:
- name: X-Forwarded-Proto
value: https
initialDelaySeconds: 3 initialDelaySeconds: 3
periodSeconds: 5 periodSeconds: 5
volumes: volumes:
@@ -273,7 +265,7 @@ spec:
entryPoints: entryPoints:
- websecure - websecure
routes: routes:
- match: (Host(`bluejay.dev`) || Host(`www.bluejay.dev`)) && (Method(`GET`) || Method(`HEAD`)) - match: Host(`bluejay.dev`) || Host(`www.bluejay.dev`)
kind: Rule kind: Rule
services: services:
- name: andrew-web - name: andrew-web

View File

@@ -201,8 +201,6 @@ spec:
metadata: metadata:
labels: labels:
app: dustin-web app: dustin-web
annotations:
flowercore.io/healthz-auth-policy: "allow-anonymous"
spec: spec:
containers: containers:
- name: nginx - name: nginx
@@ -227,18 +225,12 @@ spec:
httpGet: httpGet:
path: /healthz path: /healthz
port: 80 port: 80
httpHeaders:
- name: X-Forwarded-Proto
value: https
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: 80 port: 80
httpHeaders:
- name: X-Forwarded-Proto
value: https
initialDelaySeconds: 3 initialDelaySeconds: 3
periodSeconds: 5 periodSeconds: 5
volumes: volumes:
@@ -273,7 +265,7 @@ spec:
entryPoints: entryPoints:
- websecure - websecure
routes: routes:
- match: (Host(`timeforta.co`) || Host(`www.timeforta.co`)) && (Method(`GET`) || Method(`HEAD`)) - match: Host(`timeforta.co`) || Host(`www.timeforta.co`)
kind: Rule kind: Rule
services: services:
- name: dustin-web - name: dustin-web

View File

@@ -201,8 +201,6 @@ spec:
metadata: metadata:
labels: labels:
app: erik-web app: erik-web
annotations:
flowercore.io/healthz-auth-policy: "allow-anonymous"
spec: spec:
containers: containers:
- name: nginx - name: nginx
@@ -227,18 +225,12 @@ spec:
httpGet: httpGet:
path: /healthz path: /healthz
port: 80 port: 80
httpHeaders:
- name: X-Forwarded-Proto
value: https
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: 80 port: 80
httpHeaders:
- name: X-Forwarded-Proto
value: https
initialDelaySeconds: 3 initialDelaySeconds: 3
periodSeconds: 5 periodSeconds: 5
volumes: volumes:
@@ -273,7 +265,7 @@ spec:
entryPoints: entryPoints:
- websecure - websecure
routes: routes:
- match: (Host(`erckak.dev`) || Host(`www.erckak.dev`)) && (Method(`GET`) || Method(`HEAD`)) - match: Host(`erckak.dev`) || Host(`www.erckak.dev`)
kind: Rule kind: Rule
services: services:
- name: erik-web - name: erik-web

View File

@@ -203,8 +203,6 @@ spec:
metadata: metadata:
labels: labels:
app: fc-landing app: fc-landing
annotations:
flowercore.io/healthz-auth-policy: "allow-anonymous"
spec: spec:
containers: containers:
- name: nginx - name: nginx
@@ -229,18 +227,12 @@ spec:
httpGet: httpGet:
path: /healthz path: /healthz
port: 80 port: 80
httpHeaders:
- name: X-Forwarded-Proto
value: https
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: 80 port: 80
httpHeaders:
- name: X-Forwarded-Proto
value: https
initialDelaySeconds: 3 initialDelaySeconds: 3
periodSeconds: 5 periodSeconds: 5
volumes: volumes:
@@ -306,7 +298,7 @@ spec:
entryPoints: entryPoints:
- websecure - websecure
routes: routes:
- match: (Host(`flowercore.io`) || Host(`www.flowercore.io`)) && (Method(`GET`) || Method(`HEAD`)) - match: Host(`flowercore.io`) || Host(`www.flowercore.io`)
kind: Rule kind: Rule
services: services:
- name: fc-landing - name: fc-landing
@@ -324,7 +316,7 @@ spec:
entryPoints: entryPoints:
- web - web
routes: routes:
- match: (Host(`flowercore.io`) || Host(`www.flowercore.io`)) && (Method(`GET`) || Method(`HEAD`)) - match: Host(`flowercore.io`) || Host(`www.flowercore.io`)
kind: Rule kind: Rule
services: services:
- name: fc-landing - name: fc-landing

View File

@@ -201,8 +201,6 @@ spec:
metadata: metadata:
labels: labels:
app: fit-web app: fit-web
annotations:
flowercore.io/healthz-auth-policy: "allow-anonymous"
spec: spec:
containers: containers:
- name: nginx - name: nginx
@@ -227,18 +225,12 @@ spec:
httpGet: httpGet:
path: /healthz path: /healthz
port: 80 port: 80
httpHeaders:
- name: X-Forwarded-Proto
value: https
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: 80 port: 80
httpHeaders:
- name: X-Forwarded-Proto
value: https
initialDelaySeconds: 3 initialDelaySeconds: 3
periodSeconds: 5 periodSeconds: 5
volumes: volumes:
@@ -273,7 +265,7 @@ spec:
entryPoints: entryPoints:
- websecure - websecure
routes: routes:
- match: (Host(`flowerinsider.xyz`) || Host(`www.flowerinsider.xyz`)) && (Method(`GET`) || Method(`HEAD`)) - match: Host(`flowerinsider.xyz`) || Host(`www.flowerinsider.xyz`)
kind: Rule kind: Rule
services: services:
- name: fit-web - name: fit-web

View File

@@ -257,8 +257,6 @@ spec:
metadata: metadata:
labels: labels:
app: flowercore-web app: flowercore-web
annotations:
flowercore.io/healthz-auth-policy: "allow-anonymous"
spec: spec:
containers: containers:
- name: nginx - name: nginx
@@ -283,18 +281,12 @@ spec:
httpGet: httpGet:
path: /healthz path: /healthz
port: 80 port: 80
httpHeaders:
- name: X-Forwarded-Proto
value: https
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: 80 port: 80
httpHeaders:
- name: X-Forwarded-Proto
value: https
initialDelaySeconds: 3 initialDelaySeconds: 3
periodSeconds: 5 periodSeconds: 5
volumes: volumes:

View File

@@ -11,7 +11,7 @@ spec:
entryPoints: entryPoints:
- websecure - websecure
routes: routes:
- match: Host(`gitea.flowercore.io`) && (Method(`GET`) || Method(`HEAD`) || Method(`POST`) || Method(`OPTIONS`)) - match: Host(`gitea.flowercore.io`)
kind: Rule kind: Rule
services: services:
- name: gitea-http - name: gitea-http

View File

@@ -243,7 +243,7 @@ spec:
entryPoints: entryPoints:
- websecure - websecure
routes: routes:
- match: Host(`webmail.flowercore.io`) && (Method(`GET`) || Method(`HEAD`) || Method(`POST`) || Method(`OPTIONS`)) - match: Host(`webmail.flowercore.io`)
kind: Rule kind: Rule
services: services:
- name: mail-webmail - name: mail-webmail

View File

@@ -479,7 +479,7 @@ spec:
entryPoints: entryPoints:
- websecure - websecure
routes: routes:
- match: Host(`element.flowercore.io`) && (Method(`GET`) || Method(`HEAD`)) - match: Host(`element.flowercore.io`)
kind: Rule kind: Rule
services: services:
- name: element-web - name: element-web
@@ -497,7 +497,7 @@ spec:
entryPoints: entryPoints:
- websecure - websecure
routes: routes:
- match: Host(`matrix.flowercore.io`) && (Method(`GET`) || Method(`HEAD`) || Method(`POST`) || Method(`OPTIONS`)) - match: Host(`matrix.flowercore.io`)
kind: Rule kind: Rule
services: services:
- name: synapse - name: synapse

View File

@@ -216,19 +216,24 @@ data:
- job_name: "pimanager-app" - job_name: "pimanager-app"
scrape_interval: 15s scrape_interval: 15s
metrics_path: /metrics metrics_path: /metrics
scheme: https
tls_config:
insecure_skip_verify: true
static_configs: static_configs:
- targets: ["10.0.58.25:5000"] - targets: ["piez.iamworkin.lan"]
labels: labels:
instance: "piez" instance: "piez"
service: "pimanager" service: "signalcontrol"
vlan: "home" vlan: "home"
device: "pi4-ezconnect" device: "pi4-ezconnect"
- targets: ["10.0.58.113:5200"] rig: "signal-b"
- targets: ["pirelay.iamworkin.lan"]
labels: labels:
instance: "pirelay" instance: "pirelay"
service: "pimanager" service: "signalcontrol"
vlan: "home" vlan: "home"
device: "pi3-ks0212" device: "pi3-ks0212"
rig: "signal-a"
# Epson ET-3750 EcoTank Printer SNMP # Epson ET-3750 EcoTank Printer SNMP
- job_name: "snmp-printer" - job_name: "snmp-printer"
@@ -488,6 +493,12 @@ data:
- "https://desktop.iamworkin.lan/" - "https://desktop.iamworkin.lan/"
- "https://print.iamworkin.lan/healthz" # root 401 behind API key auth; /healthz anonymous 200 - "https://print.iamworkin.lan/healthz" # root 401 behind API key auth; /healthz anonymous 200
- "https://dns.iamworkin.lan/healthz" # root auth-gated by OIDC; /healthz anonymous 200 - "https://dns.iamworkin.lan/healthz" # root auth-gated by OIDC; /healthz anonymous 200
- "https://signalcontrol.iamworkin.lan/health" # FlowerCore.SignalControl Pi control plane
- "https://flowercore.iamworkin.lan/healthz" # FlowerCore landing
- "https://replay.iamworkin.lan/healthz" # FlowerCore.Signage replay surface
- "https://worldbuilder.iamworkin.lan/healthz" # FlowerCore.WorldBuilder
- "https://updates.iamworkin.lan/api/v1/manifests/_schema" # UpdateCenter plural LAN alias
- "https://updatecenter-internal.iamworkin.lan/api/v1/manifests/_schema" # internal UC schema route
- "https://chat.iamworkin.lan/healthz" # OIDC staged; keep blackbox off root before enforcement flips - "https://chat.iamworkin.lan/healthz" # OIDC staged; keep blackbox off root before enforcement flips
- "https://dist.iamworkin.lan/healthz" # root/admin auth-gated by OIDC; /healthz anonymous 200 - "https://dist.iamworkin.lan/healthz" # root/admin auth-gated by OIDC; /healthz anonymous 200
- "https://dms.iamworkin.lan/healthz" # future OIDC posture; health route is already anonymous/live - "https://dms.iamworkin.lan/healthz" # future OIDC posture; health route is already anonymous/live
@@ -911,12 +922,13 @@ data:
# of idle and SNMP times out, so 5m for: would page nightly. A # of idle and SNMP times out, so 5m for: would page nightly. A
# genuine printer outage (jam, disconnected) lasts well over 30m. # genuine printer outage (jam, disconnected) lasts well over 30m.
- alert: EpsonPrinterDown - alert: EpsonPrinterDown
expr: up{job="snmp-printer"} == 0 expr: (max_over_time(up{job="snmp-printer"}[35m]) == bool 0) == 1 and (hour() >= 13 or hour() < 1)
for: 30m for: 30m
labels: labels:
severity: warning severity: info
alert_channel: irc
annotations: annotations:
summary: "Epson ET-3750 SNMP unreachable for >30m (likely actual fault, not sleep)" summary: "Epson ET-3750 SNMP unreachable during waking hours (30m)"
- alert: SynologyDiskLow - alert: SynologyDiskLow
expr: hrStorageUsed{job="snmp-nas"} / hrStorageSize{job="snmp-nas"} * 100 > 85 expr: hrStorageUsed{job="snmp-nas"} / hrStorageSize{job="snmp-nas"} * 100 > 85

View File

@@ -134,8 +134,6 @@ spec:
metadata: metadata:
labels: labels:
app: pki-web app: pki-web
annotations:
flowercore.io/healthz-auth-policy: "allow-anonymous"
spec: spec:
containers: containers:
- name: nginx - name: nginx
@@ -160,18 +158,12 @@ spec:
httpGet: httpGet:
path: /healthz path: /healthz
port: 80 port: 80
httpHeaders:
- name: X-Forwarded-Proto
value: https
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: 80 port: 80
httpHeaders:
- name: X-Forwarded-Proto
value: https
initialDelaySeconds: 3 initialDelaySeconds: 3
periodSeconds: 5 periodSeconds: 5
volumes: volumes:
@@ -209,7 +201,6 @@ spec:
dnsNames: dnsNames:
- pki.iamworkin.lan - pki.iamworkin.lan
--- ---
# Internal-only route: if a public twin is ever operator-approved, gate it with Host(`<public-host>`) && (Method(`GET`) || Method(`HEAD`)).
# Traefik IngressRoute # Traefik IngressRoute
apiVersion: traefik.io/v1alpha1 apiVersion: traefik.io/v1alpha1
kind: IngressRoute kind: IngressRoute

View File

@@ -207,18 +207,12 @@ spec:
httpGet: httpGet:
path: /health path: /health
port: 5100 port: 5100
httpHeaders:
- name: X-Forwarded-Proto
value: https
initialDelaySeconds: 30 initialDelaySeconds: 30
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /health path: /health
port: 5100 port: 5100
httpHeaders:
- name: X-Forwarded-Proto
value: https
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 5 periodSeconds: 5
volumes: volumes:
@@ -262,12 +256,12 @@ spec:
- websecure - websecure
routes: routes:
- kind: Rule - kind: Rule
match: Host(`telephony.flowercore.io`) && (Method(`GET`) || Method(`HEAD`) || Method(`POST`) || Method(`OPTIONS`)) match: Host(`telephony.flowercore.io`)
services: services:
- name: telephony-web - name: telephony-web
port: 5100 port: 5100
- kind: Rule - kind: Rule
match: Host(`telephony.iamwork.in`) && (Method(`GET`) || Method(`HEAD`) || Method(`POST`) || Method(`OPTIONS`)) match: Host(`telephony.iamwork.in`)
services: services:
- name: telephony-web - name: telephony-web
port: 5100 port: 5100

View File

@@ -21,7 +21,6 @@ spec:
basicAuth: basicAuth:
secret: traefik-dashboard-auth secret: traefik-dashboard-auth
--- ---
# Internal-only route: if a public twin is ever operator-approved, gate it with Host(`<public-host>`) && (Method(`GET`) || Method(`HEAD`)).
# Dashboard IngressRoute # Dashboard IngressRoute
apiVersion: traefik.io/v1alpha1 apiVersion: traefik.io/v1alpha1
kind: IngressRoute kind: IngressRoute

View File

@@ -66,7 +66,7 @@ spec:
- websecure - websecure
routes: routes:
- kind: Rule - kind: Rule
match: Host(`voice.bluejay.dev`) && (Method(`GET`) || Method(`HEAD`) || Method(`POST`) || Method(`OPTIONS`)) match: Host(`voice.bluejay.dev`)
services: services:
- name: voice-bridge - name: voice-bridge
port: 8766 port: 8766
@@ -84,7 +84,7 @@ spec:
- websecure - websecure
routes: routes:
- kind: Rule - kind: Rule
match: Host(`voice-ws.bluejay.dev`) && (Method(`GET`) || Method(`HEAD`)) match: Host(`voice-ws.bluejay.dev`)
services: services:
- name: voice-bridge - name: voice-bridge
port: 8765 port: 8765

View File

@@ -344,7 +344,6 @@ spec:
dnsNames: dnsNames:
- zabbix.iamworkin.lan - zabbix.iamworkin.lan
--- ---
# Internal-only route: if a public twin is ever operator-approved, gate it with Host(`<public-host>`) && (Method(`GET`) || Method(`HEAD`)).
# Traefik IngressRoute # Traefik IngressRoute
apiVersion: traefik.io/v1alpha1 apiVersion: traefik.io/v1alpha1
kind: IngressRoute kind: IngressRoute

View File

@@ -13,20 +13,8 @@ public sealed class FleetManifestLintTests
private static readonly HashSet<string> PublicReadOnlyHosts = new(StringComparer.Ordinal) private static readonly HashSet<string> PublicReadOnlyHosts = new(StringComparer.Ordinal)
{ {
"bluejay.dev",
"brochure.flowercore.io", "brochure.flowercore.io",
"dist.flowercore.io", "dist.flowercore.io",
"element.flowercore.io",
"erckak.dev",
"flowercore.io",
"flowerinsider.xyz",
"timeforta.co",
"voice-ws.bluejay.dev",
"www.bluejay.dev",
"www.erckak.dev",
"www.flowercore.io",
"www.flowerinsider.xyz",
"www.timeforta.co",
}; };
// Public hosts that allow a tightly bounded write surface in addition to // Public hosts that allow a tightly bounded write surface in addition to
@@ -40,40 +28,10 @@ public sealed class FleetManifestLintTests
// same bounded read-write allowlist as the LAN pair. // same bounded read-write allowlist as the LAN pair.
private static readonly HashSet<string> PublicReadWriteAllowlistHosts = new(StringComparer.Ordinal) private static readonly HashSet<string> PublicReadWriteAllowlistHosts = new(StringComparer.Ordinal)
{ {
"chat.flowercore.io",
"gitea.flowercore.io",
"matrix.flowercore.io",
"telephony.flowercore.io",
"telephony.iamwork.in",
"updatecenter.iamworkin.lan", "updatecenter.iamworkin.lan",
"updates.iamworkin.lan", "updates.iamworkin.lan",
"update.flowercore.io", "update.flowercore.io",
"updates.flowercore.io", "updates.flowercore.io",
"voice.bluejay.dev",
"webmail.flowercore.io",
};
private static readonly IReadOnlyDictionary<string, string> InfraHealthzProbeDeployments = new Dictionary<string, string>(StringComparer.Ordinal)
{
["andrew"] = "andrew-web",
["dustin"] = "dustin-web",
["erik"] = "erik-web",
["fc-landing"] = "fc-landing",
["fit"] = "fit-web",
["flowercore"] = "flowercore-web",
["pki-web"] = "pki-web",
};
private static readonly IReadOnlyDictionary<string, string> InfraForwardedProtoProbeDeployments = new Dictionary<string, string>(StringComparer.Ordinal)
{
["andrew"] = "andrew-web",
["dustin"] = "dustin-web",
["erik"] = "erik-web",
["fc-landing"] = "fc-landing",
["fit"] = "fit-web",
["flowercore"] = "flowercore-web",
["pki-web"] = "pki-web",
["telephony"] = "telephony-web",
}; };
private static readonly HashSet<string> ApiKeyProtectedDeployments = new(StringComparer.Ordinal) private static readonly HashSet<string> ApiKeyProtectedDeployments = new(StringComparer.Ordinal)
@@ -173,13 +131,8 @@ public sealed class FleetManifestLintTests
})) }))
.Where(entry => PublicReadOnlyHosts.Any(host => entry.Match.Contains($"Host(`{host}`)", StringComparison.Ordinal))) .Where(entry => PublicReadOnlyHosts.Any(host => entry.Match.Contains($"Host(`{host}`)", StringComparison.Ordinal)))
.Where(entry => !entry.Match.Contains("Method(`GET`)", StringComparison.Ordinal) .Where(entry => !entry.Match.Contains("Method(`GET`)", StringComparison.Ordinal)
|| !entry.Match.Contains("Method(`HEAD`)", StringComparison.Ordinal) || !entry.Match.Contains("Method(`HEAD`)", StringComparison.Ordinal))
|| entry.Match.Contains("Method(`POST`)", StringComparison.Ordinal) .Select(entry => $"{entry.Document.Descriptor} is missing an explicit GET/HEAD method allowlist.")
|| entry.Match.Contains("Method(`PUT`)", StringComparison.Ordinal)
|| entry.Match.Contains("Method(`PATCH`)", StringComparison.Ordinal)
|| entry.Match.Contains("Method(`DELETE`)", StringComparison.Ordinal)
|| entry.Match.Contains("Method(`OPTIONS`)", StringComparison.Ordinal))
.Select(entry => $"{entry.Document.Descriptor} must explicitly allow GET/HEAD only on a public read-only host.")
.ToList(); .ToList();
violations.Should().BeEmpty(); violations.Should().BeEmpty();
@@ -520,49 +473,6 @@ public sealed class FleetManifestLintTests
violations.Should().BeEmpty(); violations.Should().BeEmpty();
} }
[Fact]
public void AuthSafeInfraHealthzProbes_MustDeclareAnonymousHealthzContract()
{
var violations = InfraHealthzProbeDeployments.SelectMany(expected =>
{
var deployment = AppDocuments(expected.Key)
.Single(document => document.Kind == "Deployment" && document.Name == expected.Value);
var hasHealthzProbe = deployment.MainContainerMappings()
.Any(container => ProbeHttpGetPath(container, "readinessProbe") == "/healthz"
|| ProbeHttpGetPath(container, "startupProbe") == "/healthz"
|| ProbeHttpGetPath(container, "livenessProbe") == "/healthz");
return hasHealthzProbe
&& !string.Equals(PodAnnotation(deployment, "flowercore.io/healthz-auth-policy"), "allow-anonymous", StringComparison.Ordinal)
? new[] { $"{deployment.Descriptor} probes /healthz but lacks flowercore.io/healthz-auth-policy: allow-anonymous." }
: Array.Empty<string>();
}).ToList();
violations.Should().BeEmpty();
}
[Fact]
public void AuthSafeInfraHttpProbes_MustSendForwardedProtoHttpsHeader()
{
var violations = InfraForwardedProtoProbeDeployments.SelectMany(expected =>
{
var deployment = AppDocuments(expected.Key)
.Single(document => document.Kind == "Deployment" && document.Name == expected.Value);
return deployment.MainContainerMappings()
.SelectMany(container => new[] { "startupProbe", "readinessProbe", "livenessProbe" }
.Where(probeKey => ProbeHttpGetPath(container, probeKey) is "/healthz" or "/health")
.Where(probeKey => !string.Equals(ProbeHttpGetHeaderValue(container, probeKey, "X-Forwarded-Proto"), "https", StringComparison.Ordinal))
.Select(probeKey =>
{
var containerName = ManifestNodeExtensions.Scalar(container, "name") ?? "<unnamed>";
return $"{deployment.Descriptor} container '{containerName}' {probeKey} is missing X-Forwarded-Proto=https.";
}));
}).ToList();
violations.Should().BeEmpty();
}
[Fact] [Fact]
public void Knowledge_OidcEnforcement_MustKeepHealthzAnonymousContractVisibleInManifest() public void Knowledge_OidcEnforcement_MustKeepHealthzAnonymousContractVisibleInManifest()
{ {
@@ -1105,20 +1015,6 @@ public sealed class FleetManifestLintTests
: null; : null;
} }
private static string? ProbeHttpGetHeaderValue(YamlMappingNode container, string probeKey, string name)
{
if (!ManifestNodeExtensions.TryGetMapping(container, probeKey, out var probe)
|| !ManifestNodeExtensions.TryGetMapping(probe, "httpGet", out var httpGet))
{
return null;
}
return ManifestNodeExtensions.MappingSequence(httpGet, "httpHeaders")
.Where(header => string.Equals(ManifestNodeExtensions.Scalar(header, "name"), name, StringComparison.Ordinal))
.Select(header => ManifestNodeExtensions.Scalar(header, "value"))
.SingleOrDefault();
}
private static IReadOnlyList<ManifestDocument> FcDeviceManagementDocuments() private static IReadOnlyList<ManifestDocument> FcDeviceManagementDocuments()
{ {
return Inventory.Documents return Inventory.Documents