Compare commits
1 Commits
codex/s60-
...
735f998197
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
735f998197 |
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -74,14 +74,6 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
itemPath: "vaults/IAmWorkin/items/FlowerCore Edition Signing Key - edition:aistation-field"
|
itemPath: "vaults/IAmWorkin/items/FlowerCore Edition Signing Key - edition:aistation-field"
|
||||||
---
|
---
|
||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: distribution-oidc-client
|
|
||||||
namespace: fc-distribution
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/distribution-oidc-client"
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -109,7 +101,6 @@ spec:
|
|||||||
prometheus.io/scrape: "true"
|
prometheus.io/scrape: "true"
|
||||||
prometheus.io/port: "8080"
|
prometheus.io/port: "8080"
|
||||||
prometheus.io/path: "/metrics"
|
prometheus.io/path: "/metrics"
|
||||||
flowercore.io/healthz-auth-policy: "allow-anonymous"
|
|
||||||
spec:
|
spec:
|
||||||
# Synology NFS export `/volume1/kubernetes` ACL only allows rke2-server
|
# Synology NFS export `/volume1/kubernetes` ACL only allows rke2-server
|
||||||
# (10.0.56.11) right now. Until the ACL is widened in DSM (admin only),
|
# (10.0.56.11) right now. Until the ACL is widened in DSM (admin only),
|
||||||
@@ -127,7 +118,7 @@ spec:
|
|||||||
# dotnet.exe publish -c Release -o deploy/app \
|
# dotnet.exe publish -c Release -o deploy/app \
|
||||||
# src/FlowerCore.Distribution.Web/FlowerCore.Distribution.Web.csproj
|
# src/FlowerCore.Distribution.Web/FlowerCore.Distribution.Web.csproj
|
||||||
# podman build -t localhost/fc-distribution:v<tag> -f deploy/Dockerfile.deploy deploy
|
# podman build -t localhost/fc-distribution:v<tag> -f deploy/Dockerfile.deploy deploy
|
||||||
image: localhost/fc-distribution:v20260604-oidc-root-anon
|
image: localhost/fc-distribution:v202605061948
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 8080
|
||||||
@@ -139,11 +130,13 @@ spec:
|
|||||||
value: "Production"
|
value: "Production"
|
||||||
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
|
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
|
||||||
value: "false"
|
value: "false"
|
||||||
# Authentik/OIDC enforcement. Public read/entitlement + the
|
# Authentik/OIDC enforcement (flipped ON 2026-06-04, no-live-proof per operator;
|
||||||
# dist.flowercore.io Method() allowlist stay open; OIDC gates the
|
# public read/entitlement + Method() allowlist stay open — OIDC gates admin only).
|
||||||
# operator/admin surface while /healthz remains anonymous.
|
# Auth__Enabled reverted to false 2026-06-04: enabling it gated the
|
||||||
|
# /healthz readiness probe (probe->302->NotReady->endpoints drop->down).
|
||||||
|
# Re-enable once /healthz is AllowAnonymous (falcon OIDC lane).
|
||||||
- name: FlowerCore__Auth__Enabled
|
- name: FlowerCore__Auth__Enabled
|
||||||
value: "true"
|
value: "false"
|
||||||
- name: FlowerCore__Auth__Oidc__Enabled
|
- name: FlowerCore__Auth__Oidc__Enabled
|
||||||
value: "true"
|
value: "true"
|
||||||
- name: FlowerCore__Auth__Oidc__Authority
|
- name: FlowerCore__Auth__Oidc__Authority
|
||||||
|
|||||||
@@ -1,481 +0,0 @@
|
|||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
name: fc-dns
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
---
|
|
||||||
# 1Password-backed Secret for the pfSense admin password.
|
|
||||||
# The operator watches this CRD, resolves the vault item, and produces a
|
|
||||||
# K8s Secret of the same name with each 1P field as a key. The `password`
|
|
||||||
# field of the "pfSense Admin" item becomes Secret key `password`.
|
|
||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: pfsense-admin
|
|
||||||
namespace: fc-dns
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/pfSense Admin"
|
|
||||||
---
|
|
||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: dns-oidc-client
|
|
||||||
namespace: fc-dns
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/dns-oidc-client"
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: dns-web-data
|
|
||||||
namespace: fc-dns
|
|
||||||
spec:
|
|
||||||
accessModes: [ReadWriteOnce]
|
|
||||||
storageClassName: longhorn
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 1Gi
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: dns-web-config
|
|
||||||
namespace: fc-dns
|
|
||||||
data:
|
|
||||||
appsettings.Production.json: |
|
|
||||||
{
|
|
||||||
"FlowerCore": {
|
|
||||||
"Auth": {
|
|
||||||
"Enabled": true,
|
|
||||||
"Oidc": {
|
|
||||||
"Enabled": true,
|
|
||||||
"Audience": "dns",
|
|
||||||
"RequireHttpsMetadata": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Database": {
|
|
||||||
"Provider": "Sqlite",
|
|
||||||
"ConnectionStrings": {
|
|
||||||
"Sqlite": "Data Source=/data/dns.db"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Tenant": {
|
|
||||||
"DefaultTenantId": "default",
|
|
||||||
"JwtClaimsEnabled": false,
|
|
||||||
"DefaultTenantHosts": [
|
|
||||||
"dns.iamworkin.lan"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Audit": {
|
|
||||||
"HashChain": {
|
|
||||||
"BridgeSensitivity": {
|
|
||||||
"Distribution": "Warn"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: dns-web
|
|
||||||
namespace: fc-dns
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: dns-web
|
|
||||||
app.kubernetes.io/managed-by: flowercore
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
strategy:
|
|
||||||
type: Recreate
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app.kubernetes.io/name: dns-web
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: dns-web
|
|
||||||
annotations:
|
|
||||||
prometheus.io/scrape: "true"
|
|
||||||
prometheus.io/port: "5320"
|
|
||||||
prometheus.io/path: "/metrics/prometheus"
|
|
||||||
flowercore.io/healthz-auth-policy: "allow-anonymous"
|
|
||||||
spec:
|
|
||||||
serviceAccountName: dns-web
|
|
||||||
securityContext:
|
|
||||||
runAsNonRoot: true
|
|
||||||
runAsUser: 1654
|
|
||||||
runAsGroup: 1654
|
|
||||||
fsGroup: 1654
|
|
||||||
containers:
|
|
||||||
- name: dns-web
|
|
||||||
image: localhost/fc-dns-web:v20260604-oidc-proper
|
|
||||||
imagePullPolicy: Never
|
|
||||||
securityContext:
|
|
||||||
readOnlyRootFilesystem: true
|
|
||||||
allowPrivilegeEscalation: false
|
|
||||||
capabilities:
|
|
||||||
drop: [ALL]
|
|
||||||
ports:
|
|
||||||
- containerPort: 5320
|
|
||||||
env:
|
|
||||||
# pfSense admin password resolved by the 1Password operator.
|
|
||||||
# `FallbackPassword` is the Slice A seam exposed by
|
|
||||||
# OptionsFallbackPasswordResolver; Slice B will replace it with
|
|
||||||
# a pull-at-runtime 1P Connect resolver once Shared.Vault ships.
|
|
||||||
- name: FlowerCore__Dns__Providers__PfSenseUnbound__FallbackPassword
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: pfsense-admin
|
|
||||||
key: password
|
|
||||||
- name: FlowerCore__Auth__Oidc__Authority
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: dns-oidc-client
|
|
||||||
key: issuer_url
|
|
||||||
optional: true
|
|
||||||
- name: FlowerCore__Auth__Oidc__ClientId
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: dns-oidc-client
|
|
||||||
key: client_id
|
|
||||||
optional: true
|
|
||||||
- name: FlowerCore__Auth__Oidc__ClientSecret
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: dns-oidc-client
|
|
||||||
key: client_secret
|
|
||||||
optional: true
|
|
||||||
- name: FlowerCore__Auth__Enabled
|
|
||||||
value: "true"
|
|
||||||
- name: FlowerCore__Auth__Oidc__Enabled
|
|
||||||
value: "true"
|
|
||||||
- name: FlowerCore__Auth__Oidc__Audience
|
|
||||||
value: "dns"
|
|
||||||
volumeMounts:
|
|
||||||
- name: data
|
|
||||||
mountPath: /data
|
|
||||||
- name: tmp
|
|
||||||
mountPath: /tmp
|
|
||||||
- name: logs
|
|
||||||
mountPath: /app/logs
|
|
||||||
- name: config
|
|
||||||
mountPath: /app/appsettings.Production.json
|
|
||||||
subPath: appsettings.Production.json
|
|
||||||
readOnly: true
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: 50m
|
|
||||||
memory: 96Mi
|
|
||||||
limits:
|
|
||||||
cpu: 300m
|
|
||||||
memory: 384Mi
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /healthz
|
|
||||||
port: 5320
|
|
||||||
initialDelaySeconds: 10
|
|
||||||
periodSeconds: 10
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /healthz
|
|
||||||
port: 5320
|
|
||||||
initialDelaySeconds: 20
|
|
||||||
periodSeconds: 30
|
|
||||||
volumes:
|
|
||||||
- name: data
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: dns-web-data
|
|
||||||
- name: tmp
|
|
||||||
emptyDir: {}
|
|
||||||
- name: logs
|
|
||||||
emptyDir: {}
|
|
||||||
- name: config
|
|
||||||
configMap:
|
|
||||||
name: dns-web-config
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: dns-web
|
|
||||||
namespace: fc-dns
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app.kubernetes.io/name: dns-web
|
|
||||||
ports:
|
|
||||||
- port: 5320
|
|
||||||
targetPort: 5320
|
|
||||||
type: ClusterIP
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ServiceAccount
|
|
||||||
metadata:
|
|
||||||
name: dns-web
|
|
||||||
namespace: fc-dns
|
|
||||||
---
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: ClusterRole
|
|
||||||
metadata:
|
|
||||||
name: dns-web
|
|
||||||
rules:
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources: ["namespaces", "pods", "services", "secrets", "configmaps"]
|
|
||||||
verbs: ["get", "list", "watch"]
|
|
||||||
- apiGroups: ["cert-manager.io"]
|
|
||||||
resources: ["certificates"]
|
|
||||||
verbs: ["get", "list", "watch"]
|
|
||||||
---
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: ClusterRoleBinding
|
|
||||||
metadata:
|
|
||||||
name: dns-web
|
|
||||||
subjects:
|
|
||||||
- kind: ServiceAccount
|
|
||||||
name: dns-web
|
|
||||||
namespace: fc-dns
|
|
||||||
roleRef:
|
|
||||||
kind: ClusterRole
|
|
||||||
name: dns-web
|
|
||||||
apiGroup: rbac.authorization.k8s.io
|
|
||||||
---
|
|
||||||
apiVersion: cert-manager.io/v1
|
|
||||||
kind: Certificate
|
|
||||||
metadata:
|
|
||||||
name: dns-web-cert
|
|
||||||
namespace: fc-dns
|
|
||||||
spec:
|
|
||||||
secretName: dns-web-tls
|
|
||||||
issuerRef:
|
|
||||||
name: step-ca-dns01
|
|
||||||
kind: ClusterIssuer
|
|
||||||
dnsNames:
|
|
||||||
- dns.iamworkin.lan
|
|
||||||
duration: 720h
|
|
||||||
renewBefore: 240h
|
|
||||||
---
|
|
||||||
apiVersion: traefik.io/v1alpha1
|
|
||||||
kind: IngressRoute
|
|
||||||
metadata:
|
|
||||||
name: dns-web
|
|
||||||
namespace: fc-dns
|
|
||||||
spec:
|
|
||||||
entryPoints: [websecure]
|
|
||||||
routes:
|
|
||||||
- match: Host(`dns.iamworkin.lan`)
|
|
||||||
kind: Rule
|
|
||||||
services:
|
|
||||||
- name: dns-web
|
|
||||||
port: 5320
|
|
||||||
tls:
|
|
||||||
secretName: dns-web-tls
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ServiceAccount
|
|
||||||
metadata:
|
|
||||||
name: dns-acme-webhook
|
|
||||||
namespace: fc-dns
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: dns-acme-webhook
|
|
||||||
namespace: fc-dns
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: dns-acme-webhook
|
|
||||||
app.kubernetes.io/managed-by: flowercore
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app.kubernetes.io/name: dns-acme-webhook
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: dns-acme-webhook
|
|
||||||
spec:
|
|
||||||
serviceAccountName: dns-acme-webhook
|
|
||||||
securityContext:
|
|
||||||
runAsNonRoot: true
|
|
||||||
runAsUser: 1654
|
|
||||||
runAsGroup: 1654
|
|
||||||
fsGroup: 1654
|
|
||||||
containers:
|
|
||||||
- name: dns-acme-webhook
|
|
||||||
image: localhost/fc-dns-acme-webhook:v202604290845
|
|
||||||
imagePullPolicy: Never
|
|
||||||
securityContext:
|
|
||||||
readOnlyRootFilesystem: true
|
|
||||||
allowPrivilegeEscalation: false
|
|
||||||
capabilities:
|
|
||||||
drop: [ALL]
|
|
||||||
ports:
|
|
||||||
- containerPort: 9443
|
|
||||||
name: https
|
|
||||||
env:
|
|
||||||
- name: ASPNETCORE_URLS
|
|
||||||
value: https://+:9443
|
|
||||||
- name: Kestrel__Certificates__Default__Path
|
|
||||||
value: /tls/tls.crt
|
|
||||||
- name: Kestrel__Certificates__Default__KeyPath
|
|
||||||
value: /tls/tls.key
|
|
||||||
- name: FlowerCore__Dns__AcmeWebhook__ServiceBaseUrl
|
|
||||||
value: http://dns-web:5320
|
|
||||||
- name: FlowerCore__Dns__AcmeWebhook__GroupName
|
|
||||||
value: acme.flowercore.io
|
|
||||||
- name: FlowerCore__Dns__AcmeWebhook__SolverName
|
|
||||||
value: flowercore-dns
|
|
||||||
- name: FlowerCore__Dns__AcmeWebhook__Version
|
|
||||||
value: v1alpha1
|
|
||||||
volumeMounts:
|
|
||||||
- name: tls
|
|
||||||
mountPath: /tls
|
|
||||||
readOnly: true
|
|
||||||
- name: tmp
|
|
||||||
mountPath: /tmp
|
|
||||||
- name: logs
|
|
||||||
mountPath: /app/logs
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: 25m
|
|
||||||
memory: 64Mi
|
|
||||||
limits:
|
|
||||||
cpu: 200m
|
|
||||||
memory: 256Mi
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
scheme: HTTPS
|
|
||||||
path: /readyz
|
|
||||||
port: https
|
|
||||||
initialDelaySeconds: 5
|
|
||||||
periodSeconds: 10
|
|
||||||
timeoutSeconds: 5
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
scheme: HTTPS
|
|
||||||
path: /healthz
|
|
||||||
port: https
|
|
||||||
initialDelaySeconds: 10
|
|
||||||
periodSeconds: 20
|
|
||||||
timeoutSeconds: 5
|
|
||||||
volumes:
|
|
||||||
- name: tls
|
|
||||||
secret:
|
|
||||||
secretName: dns-acme-webhook-tls
|
|
||||||
- name: tmp
|
|
||||||
emptyDir: {}
|
|
||||||
- name: logs
|
|
||||||
emptyDir: {}
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: dns-acme-webhook
|
|
||||||
namespace: fc-dns
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app.kubernetes.io/name: dns-acme-webhook
|
|
||||||
ports:
|
|
||||||
- port: 443
|
|
||||||
targetPort: https
|
|
||||||
name: https
|
|
||||||
type: ClusterIP
|
|
||||||
---
|
|
||||||
apiVersion: cert-manager.io/v1
|
|
||||||
kind: Issuer
|
|
||||||
metadata:
|
|
||||||
name: dns-acme-webhook-selfsigned
|
|
||||||
namespace: fc-dns
|
|
||||||
spec:
|
|
||||||
selfSigned: {}
|
|
||||||
---
|
|
||||||
apiVersion: cert-manager.io/v1
|
|
||||||
kind: Certificate
|
|
||||||
metadata:
|
|
||||||
name: dns-acme-webhook-ca
|
|
||||||
namespace: fc-dns
|
|
||||||
spec:
|
|
||||||
secretName: dns-acme-webhook-ca
|
|
||||||
duration: 43800h
|
|
||||||
issuerRef:
|
|
||||||
name: dns-acme-webhook-selfsigned
|
|
||||||
commonName: ca.dns-acme-webhook.fc-dns
|
|
||||||
isCA: true
|
|
||||||
---
|
|
||||||
apiVersion: cert-manager.io/v1
|
|
||||||
kind: Issuer
|
|
||||||
metadata:
|
|
||||||
name: dns-acme-webhook-ca-issuer
|
|
||||||
namespace: fc-dns
|
|
||||||
spec:
|
|
||||||
ca:
|
|
||||||
secretName: dns-acme-webhook-ca
|
|
||||||
---
|
|
||||||
apiVersion: cert-manager.io/v1
|
|
||||||
kind: Certificate
|
|
||||||
metadata:
|
|
||||||
name: dns-acme-webhook-serving-cert
|
|
||||||
namespace: fc-dns
|
|
||||||
spec:
|
|
||||||
secretName: dns-acme-webhook-tls
|
|
||||||
duration: 8760h
|
|
||||||
issuerRef:
|
|
||||||
name: dns-acme-webhook-ca-issuer
|
|
||||||
dnsNames:
|
|
||||||
- dns-acme-webhook
|
|
||||||
- dns-acme-webhook.fc-dns
|
|
||||||
- dns-acme-webhook.fc-dns.svc
|
|
||||||
---
|
|
||||||
apiVersion: apiregistration.k8s.io/v1
|
|
||||||
kind: APIService
|
|
||||||
metadata:
|
|
||||||
name: v1alpha1.acme.flowercore.io
|
|
||||||
annotations:
|
|
||||||
cert-manager.io/inject-ca-from: fc-dns/dns-acme-webhook-serving-cert
|
|
||||||
spec:
|
|
||||||
group: acme.flowercore.io
|
|
||||||
groupPriorityMinimum: 1000
|
|
||||||
service:
|
|
||||||
name: dns-acme-webhook
|
|
||||||
namespace: fc-dns
|
|
||||||
version: v1alpha1
|
|
||||||
versionPriority: 15
|
|
||||||
---
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: ClusterRole
|
|
||||||
metadata:
|
|
||||||
name: dns-acme-webhook-solver
|
|
||||||
rules:
|
|
||||||
- apiGroups: ["acme.flowercore.io"]
|
|
||||||
resources: ["flowercore-dns"]
|
|
||||||
verbs: ["create"]
|
|
||||||
---
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: ClusterRoleBinding
|
|
||||||
metadata:
|
|
||||||
name: dns-acme-webhook-solver
|
|
||||||
subjects:
|
|
||||||
- kind: ServiceAccount
|
|
||||||
name: cert-manager
|
|
||||||
namespace: cert-manager
|
|
||||||
roleRef:
|
|
||||||
kind: ClusterRole
|
|
||||||
name: dns-acme-webhook-solver
|
|
||||||
apiGroup: rbac.authorization.k8s.io
|
|
||||||
---
|
|
||||||
apiVersion: cert-manager.io/v1
|
|
||||||
kind: ClusterIssuer
|
|
||||||
metadata:
|
|
||||||
name: step-ca-dns01
|
|
||||||
spec:
|
|
||||||
acme:
|
|
||||||
caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ4RENDQVdxZ0F3SUJBZ0lSQVBZMzU3RzZvdzZ6TUFMNSs0YlMya2t3Q2dZSUtvWkl6ajBFQXdJd1FERWEKTUJnR0ExVUVDaE1SU1VGdFYyOXlhMmx1SUVGRFRVVWdRMEV4SWpBZ0JnTlZCQU1UR1VsQmJWZHZjbXRwYmlCQgpRMDFGSUVOQklGSnZiM1FnUTBFd0hoY05Nall3TXpBNE1UZ3dOekV4V2hjTk16WXdNekExTVRnd056RXhXakJBCk1Sb3dHQVlEVlFRS0V4RkpRVzFYYjNKcmFXNGdRVU5OUlNCRFFURWlNQ0FHQTFVRUF4TVpTVUZ0VjI5eWEybHUKSUVGRFRVVWdRMEVnVW05dmRDQkRRVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSjJuMDRYMQpKWm81WmRxL2kxSWR2OCtmcXdaeUF6Qmg3d2hicWowU1dzSkw4VVdSYWJDTXFZQ3M3K2RYTzB4UlN6cWt3RkRMCngrdm9vT2FpOFJnUk5oYWpSVEJETUE0R0ExVWREd0VCL3dRRUF3SUJCakFTQmdOVkhSTUJBZjhFQ0RBR0FRSC8KQWdFQk1CMEdBMVVkRGdRV0JCUm51UFBRUjZpTS9INnZPbHVpVTNTeWdheXo4akFLQmdncWhrak9QUVFEQWdOSQpBREJGQWlFQXJRSzlkWVBHbUFac2RZbmp6aXVGVlZFNU5LWlVjY2VZdkdmR0MrdExYVXNDSUF1ZEYyekpyQ1JxCjNtSzUwWlpFVC9md1RrSndpRUY0ODI0bWpQOHAxQ0tNCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
|
|
||||||
privateKeySecretRef:
|
|
||||||
name: step-ca-dns01-account-key
|
|
||||||
server: https://10.0.56.10:9443/acme/acme/directory
|
|
||||||
solvers:
|
|
||||||
- dns01:
|
|
||||||
webhook:
|
|
||||||
groupName: acme.flowercore.io
|
|
||||||
solverName: flowercore-dns
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# ArgoCD's bluejay-infra ApplicationSet discovers apps/* directories on main.
|
|
||||||
# The kustomization is included for local previews and single-app validation.
|
|
||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
||||||
kind: Kustomization
|
|
||||||
resources:
|
|
||||||
- fc-dns.yaml
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -1,296 +0,0 @@
|
|||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
name: fc-media
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: fc-media
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
---
|
|
||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: media-oidc-client
|
|
||||||
namespace: fc-media
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: fc-media-web
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/media-oidc-client"
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: fc-media-config
|
|
||||||
namespace: fc-media
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: fc-media-web
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
data:
|
|
||||||
appsettings.Production.json: |
|
|
||||||
{
|
|
||||||
"DatabaseProvider": "Sqlite",
|
|
||||||
"ConnectionStrings": {
|
|
||||||
"Sqlite": "Data Source=/data/media.db"
|
|
||||||
},
|
|
||||||
"FlowerCore": {
|
|
||||||
"Auth": {
|
|
||||||
"Enabled": true,
|
|
||||||
"Oidc": {
|
|
||||||
"Authority": "https://id.iamworkin.lan/application/o/media/",
|
|
||||||
"ClientId": "media",
|
|
||||||
"ClientSecret": "",
|
|
||||||
"Audience": "media",
|
|
||||||
"RequireHttpsMetadata": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Tenant": {
|
|
||||||
"JwtClaimsEnabled": false,
|
|
||||||
"DefaultTenantHosts": [ "media.iamworkin.lan" ]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Media": {
|
|
||||||
"LibraryRoot": "/media/library",
|
|
||||||
"Sources": [
|
|
||||||
{
|
|
||||||
"Name": "BlueJayNAS Video",
|
|
||||||
"Driver": "Nfs",
|
|
||||||
"MountedPath": "/media/library",
|
|
||||||
"RemotePath": "nfs://10.0.58.3/volume1/video",
|
|
||||||
"IsEnabled": true,
|
|
||||||
"IsDefault": true,
|
|
||||||
"Notes": "Synology NFS media share mounted read-only inside the cluster."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"GeneratedRoot": "/data/generated",
|
|
||||||
"TranscodeRoot": "/data/transcodes",
|
|
||||||
"InboxPath": "/media/inbox",
|
|
||||||
"InboxScanIntervalMinutes": 5,
|
|
||||||
"ScanOnStartup": false,
|
|
||||||
"ComputeChecksums": false,
|
|
||||||
"FfmpegCommand": "ffmpeg",
|
|
||||||
"FfprobeCommand": "ffprobe",
|
|
||||||
"Hls": {
|
|
||||||
"MaxConcurrentJobs": 1
|
|
||||||
},
|
|
||||||
"DefaultViewerName": "BlueJay",
|
|
||||||
"Dlna": {
|
|
||||||
"IsEnabled": true,
|
|
||||||
"MulticastAddress": "239.255.255.250",
|
|
||||||
"Port": 1900,
|
|
||||||
"DiscoveryTimeoutSeconds": 2,
|
|
||||||
"DescriptionFetchTimeoutSeconds": 2,
|
|
||||||
"MaxResponsesPerSearchTarget": 32,
|
|
||||||
"SearchTargets": [
|
|
||||||
"urn:schemas-upnp-org:device:MediaRenderer:1",
|
|
||||||
"urn:schemas-upnp-org:device:MediaServer:1"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: fc-media-data
|
|
||||||
namespace: fc-media
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: fc-media-web
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteOnce
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 20Gi
|
|
||||||
storageClassName: longhorn
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: fc-media-web
|
|
||||||
namespace: fc-media
|
|
||||||
labels:
|
|
||||||
app: fc-media-web
|
|
||||||
app.kubernetes.io/name: fc-media-web
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
strategy:
|
|
||||||
type: Recreate
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: fc-media-web
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: fc-media-web
|
|
||||||
app.kubernetes.io/name: fc-media-web
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
annotations:
|
|
||||||
prometheus.io/scrape: "true"
|
|
||||||
prometheus.io/port: "5200"
|
|
||||||
prometheus.io/path: "/metrics"
|
|
||||||
flowercore.io/healthz-auth-policy: "allow-anonymous"
|
|
||||||
spec:
|
|
||||||
nodeSelector:
|
|
||||||
kubernetes.io/hostname: rke2-server
|
|
||||||
containers:
|
|
||||||
- name: fc-media-web
|
|
||||||
image: localhost/fc-media-web:v20260604-oidc-proper
|
|
||||||
imagePullPolicy: Never
|
|
||||||
ports:
|
|
||||||
- containerPort: 5200
|
|
||||||
name: http
|
|
||||||
env:
|
|
||||||
- name: ASPNETCORE_ENVIRONMENT
|
|
||||||
value: Production
|
|
||||||
- name: ASPNETCORE_URLS
|
|
||||||
value: http://+:5200
|
|
||||||
- name: FlowerCore__Auth__Enabled
|
|
||||||
value: "true"
|
|
||||||
- name: FlowerCore__Auth__Oidc__Enabled
|
|
||||||
value: "true"
|
|
||||||
- name: FlowerCore__Auth__Oidc__Audience
|
|
||||||
value: "media"
|
|
||||||
- name: FlowerCore__Auth__Oidc__ClientId
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: media-oidc-client
|
|
||||||
key: client_id
|
|
||||||
optional: true
|
|
||||||
- name: FlowerCore__Auth__Oidc__ClientSecret
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: media-oidc-client
|
|
||||||
key: client_secret
|
|
||||||
optional: true
|
|
||||||
- name: FlowerCore__Auth__Oidc__Authority
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: media-oidc-client
|
|
||||||
key: issuer_url
|
|
||||||
optional: true
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: 500m
|
|
||||||
memory: 1Gi
|
|
||||||
limits:
|
|
||||||
cpu: "4"
|
|
||||||
memory: 4Gi
|
|
||||||
volumeMounts:
|
|
||||||
- name: config
|
|
||||||
mountPath: /app/appsettings.Production.json
|
|
||||||
subPath: appsettings.Production.json
|
|
||||||
readOnly: true
|
|
||||||
- name: data
|
|
||||||
mountPath: /data
|
|
||||||
- name: transcodes
|
|
||||||
mountPath: /data/transcodes
|
|
||||||
- name: media-library
|
|
||||||
mountPath: /media/library
|
|
||||||
readOnly: true
|
|
||||||
- name: media-inbox
|
|
||||||
mountPath: /media/inbox
|
|
||||||
startupProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /healthz
|
|
||||||
port: 5200
|
|
||||||
httpHeaders:
|
|
||||||
- name: X-Forwarded-Proto
|
|
||||||
value: https
|
|
||||||
failureThreshold: 18
|
|
||||||
periodSeconds: 10
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /healthz
|
|
||||||
port: 5200
|
|
||||||
httpHeaders:
|
|
||||||
- name: X-Forwarded-Proto
|
|
||||||
value: https
|
|
||||||
initialDelaySeconds: 5
|
|
||||||
periodSeconds: 10
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /healthz
|
|
||||||
port: 5200
|
|
||||||
httpHeaders:
|
|
||||||
- name: X-Forwarded-Proto
|
|
||||||
value: https
|
|
||||||
initialDelaySeconds: 30
|
|
||||||
periodSeconds: 30
|
|
||||||
volumes:
|
|
||||||
- name: config
|
|
||||||
configMap:
|
|
||||||
name: fc-media-config
|
|
||||||
- name: data
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: fc-media-data
|
|
||||||
- name: transcodes
|
|
||||||
nfs:
|
|
||||||
server: 10.0.58.3
|
|
||||||
path: /volume1/kubernetes/fc-media-transcodes
|
|
||||||
- name: media-inbox
|
|
||||||
nfs:
|
|
||||||
server: 10.0.58.3
|
|
||||||
path: /volume1/kubernetes/fc-media-inbox
|
|
||||||
- name: media-library
|
|
||||||
nfs:
|
|
||||||
server: 10.0.58.3
|
|
||||||
path: /volume1/video
|
|
||||||
readOnly: true
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: fc-media-web
|
|
||||||
namespace: fc-media
|
|
||||||
labels:
|
|
||||||
app: fc-media-web
|
|
||||||
app.kubernetes.io/name: fc-media-web
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
spec:
|
|
||||||
type: ClusterIP
|
|
||||||
selector:
|
|
||||||
app: fc-media-web
|
|
||||||
ports:
|
|
||||||
- port: 5200
|
|
||||||
targetPort: 5200
|
|
||||||
protocol: TCP
|
|
||||||
name: http
|
|
||||||
---
|
|
||||||
apiVersion: cert-manager.io/v1
|
|
||||||
kind: Certificate
|
|
||||||
metadata:
|
|
||||||
name: fc-media-tls
|
|
||||||
namespace: fc-media
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: fc-media-web
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
spec:
|
|
||||||
secretName: fc-media-tls
|
|
||||||
issuerRef:
|
|
||||||
name: step-ca-acme
|
|
||||||
kind: ClusterIssuer
|
|
||||||
dnsNames:
|
|
||||||
- media.iamworkin.lan
|
|
||||||
---
|
|
||||||
apiVersion: traefik.io/v1alpha1
|
|
||||||
kind: IngressRoute
|
|
||||||
metadata:
|
|
||||||
name: fc-media-web
|
|
||||||
namespace: fc-media
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: fc-media-web
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
spec:
|
|
||||||
entryPoints:
|
|
||||||
- websecure
|
|
||||||
routes:
|
|
||||||
- match: Host(`media.iamworkin.lan`)
|
|
||||||
kind: Rule
|
|
||||||
services:
|
|
||||||
- name: fc-media-web
|
|
||||||
port: 5200
|
|
||||||
tls:
|
|
||||||
secretName: fc-media-tls
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# ArgoCD's bluejay-infra ApplicationSet discovers apps/* directories on main.
|
|
||||||
# The kustomization is included for local previews and single-app validation.
|
|
||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
||||||
kind: Kustomization
|
|
||||||
resources:
|
|
||||||
- fc-media.yaml
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -481,16 +481,16 @@ data:
|
|||||||
- "https://intranet.iamworkin.lan/"
|
- "https://intranet.iamworkin.lan/"
|
||||||
- "https://signage.iamworkin.lan/healthz" # root 401 auth-gated 2026-06-01; /healthz anon 200
|
- "https://signage.iamworkin.lan/healthz" # root 401 auth-gated 2026-06-01; /healthz anon 200
|
||||||
- "https://kiosk.iamworkin.lan/"
|
- "https://kiosk.iamworkin.lan/"
|
||||||
- "https://media.iamworkin.lan/healthz" # root auth-gated by OIDC; /healthz anonymous 200
|
- "https://media.iamworkin.lan/" # OIDC lane must add /healthz before flipping auth; live /healthz 404 on 2026-06-04
|
||||||
- "https://mysql.iamworkin.lan/healthz" # root 401 auth-gated 2026-06-01; /healthz anon 200
|
- "https://mysql.iamworkin.lan/healthz" # root 401 auth-gated 2026-06-01; /healthz anon 200
|
||||||
- "https://php.iamworkin.lan/healthz" # root 401 auth-gated 2026-06-01; /healthz anon 200
|
- "https://php.iamworkin.lan/healthz" # root 401 auth-gated 2026-06-01; /healthz anon 200
|
||||||
- "https://zabbix.iamworkin.lan/"
|
- "https://zabbix.iamworkin.lan/"
|
||||||
- "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/" # OIDC lane must add /healthz before flipping auth; live /healthz 404 on 2026-06-04
|
||||||
- "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" # distribution OIDC flip outage was /healthz gating; probe the anonymous health route
|
||||||
- "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
|
||||||
- "https://menuboard.iamworkin.lan/"
|
- "https://menuboard.iamworkin.lan/"
|
||||||
- "https://messageboard.iamworkin.lan/"
|
- "https://messageboard.iamworkin.lan/"
|
||||||
- "https://presentations.iamworkin.lan/"
|
- "https://presentations.iamworkin.lan/"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -13,20 +13,9 @@ 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",
|
"dns.iamworkin.lan",
|
||||||
"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 +29,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 +132,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 +474,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()
|
||||||
{
|
{
|
||||||
@@ -577,16 +488,16 @@ public sealed class FleetManifestLintTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Distribution_OidcEnforcement_MustKeepHealthzAnonymousContractVisibleInManifest()
|
public void Distribution_OidcEnforcement_MustStayOffUntilHealthzAllowAnonymousProofLands()
|
||||||
{
|
{
|
||||||
var distribution = Inventory.Documents
|
var distribution = Inventory.Documents
|
||||||
.Single(document => document.Kind == "Deployment" && document.Namespace == "fc-distribution" && document.Name == "fc-distribution");
|
.Single(document => document.Kind == "Deployment" && document.Namespace == "fc-distribution" && document.Name == "fc-distribution");
|
||||||
var container = distribution.MainContainerMappings().Should().ContainSingle().Subject;
|
var container = distribution.MainContainerMappings().Should().ContainSingle().Subject;
|
||||||
|
|
||||||
EnvValue(container, "FlowerCore__Auth__Oidc__Enabled").Should().Be("true");
|
EnvValue(container, "FlowerCore__Auth__Oidc__Enabled").Should().Be("true");
|
||||||
EnvValue(container, "FlowerCore__Auth__Enabled").Should().Be("true");
|
EnvValue(container, "FlowerCore__Auth__Enabled").Should().Be("false");
|
||||||
ProbeHttpGetPath(container, "readinessProbe").Should().Be("/healthz");
|
ProbeHttpGetPath(container, "readinessProbe").Should().Be("/healthz");
|
||||||
PodAnnotation(distribution, "flowercore.io/healthz-auth-policy").Should().Be("allow-anonymous");
|
PodAnnotation(distribution, "flowercore.io/healthz-auth-policy").Should().NotBe("allow-anonymous");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -871,146 +782,6 @@ public sealed class FleetManifestLintTests
|
|||||||
application.Scalar("spec", "destination", "namespace").Should().Be("fc-devicemgmt");
|
application.Scalar("spec", "destination", "namespace").Should().Be("fc-devicemgmt");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void OidcFlipServices_AreGitOpsManagedWithHealthzProbes()
|
|
||||||
{
|
|
||||||
var deployments = new[]
|
|
||||||
{
|
|
||||||
(App: "fc-dns", Name: "dns-web", Slug: "dns", Secret: "dns-oidc-client"),
|
|
||||||
(App: "fc-media", Name: "fc-media-web", Slug: "media", Secret: "media-oidc-client"),
|
|
||||||
(App: "fc-distribution", Name: "fc-distribution", Slug: "distribution", Secret: "distribution-oidc-client"),
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var expected in deployments)
|
|
||||||
{
|
|
||||||
var deployment = AppDocuments(expected.App)
|
|
||||||
.Single(document => document.Kind == "Deployment" && document.Name == expected.Name);
|
|
||||||
var container = deployment.MainContainerMappings().Should().ContainSingle().Subject;
|
|
||||||
|
|
||||||
EnvValue(container, "FlowerCore__Auth__Enabled").Should().Be("true");
|
|
||||||
EnvValue(container, "FlowerCore__Auth__Oidc__Enabled").Should().Be("true");
|
|
||||||
(EnvValue(container, "FlowerCore__Auth__Oidc__Audience") ?? EnvValue(container, "FlowerCore__Auth__Oidc__ClientId"))
|
|
||||||
.Should()
|
|
||||||
.Be(expected.Slug);
|
|
||||||
EnvSecretName(container, "FlowerCore__Auth__Oidc__ClientSecret").Should().Be(expected.Secret);
|
|
||||||
EnvSecretOptional(container, "FlowerCore__Auth__Oidc__ClientSecret").Should().Be("true");
|
|
||||||
|
|
||||||
ProbePath(container, "readinessProbe").Should().Be("/healthz");
|
|
||||||
if (ProbePath(container, "startupProbe") is { } startupProbePath)
|
|
||||||
{
|
|
||||||
startupProbePath.Should().Be("/healthz");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ProbePath(container, "livenessProbe") is { } livenessProbePath)
|
|
||||||
{
|
|
||||||
livenessProbePath.Should().Be("/healthz");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void OidcFlipServices_UseOnePasswordItemClientSecrets()
|
|
||||||
{
|
|
||||||
var expectedItems = new Dictionary<string, (string Name, string ItemPath)>(StringComparer.Ordinal)
|
|
||||||
{
|
|
||||||
["fc-dns"] = ("dns-oidc-client", "vaults/IAmWorkin/items/dns-oidc-client"),
|
|
||||||
["fc-media"] = ("media-oidc-client", "vaults/IAmWorkin/items/media-oidc-client"),
|
|
||||||
["fc-distribution"] = ("distribution-oidc-client", "vaults/IAmWorkin/items/distribution-oidc-client"),
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var expected in expectedItems)
|
|
||||||
{
|
|
||||||
var item = AppDocuments(expected.Key)
|
|
||||||
.Single(document => document.Kind == "OnePasswordItem" && document.Name == expected.Value.Name);
|
|
||||||
|
|
||||||
item.Scalar("spec", "itemPath").Should().Be(expected.Value.ItemPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void DnsAndMediaGitOpsAdoption_PreservesLiveStorageAndImageShape()
|
|
||||||
{
|
|
||||||
var dnsDeployment = AppDocuments("fc-dns")
|
|
||||||
.Single(document => document.Kind == "Deployment" && document.Name == "dns-web");
|
|
||||||
var dnsContainer = dnsDeployment.MainContainerMappings().Should().ContainSingle().Subject;
|
|
||||||
var dnsPvc = AppDocuments("fc-dns")
|
|
||||||
.Single(document => document.Kind == "PersistentVolumeClaim" && document.Name == "dns-web-data");
|
|
||||||
|
|
||||||
ManifestNodeExtensions.Scalar(dnsContainer, "image").Should().Be("localhost/fc-dns-web:v20260604-oidc-proper");
|
|
||||||
dnsPvc.Scalar("spec", "storageClassName").Should().Be("longhorn");
|
|
||||||
dnsPvc.Scalar("spec", "resources", "requests", "storage").Should().Be("1Gi");
|
|
||||||
|
|
||||||
var mediaDeployment = AppDocuments("fc-media")
|
|
||||||
.Single(document => document.Kind == "Deployment" && document.Name == "fc-media-web");
|
|
||||||
var mediaContainer = mediaDeployment.MainContainerMappings().Should().ContainSingle().Subject;
|
|
||||||
var mediaPvc = AppDocuments("fc-media")
|
|
||||||
.Single(document => document.Kind == "PersistentVolumeClaim" && document.Name == "fc-media-data");
|
|
||||||
|
|
||||||
ManifestNodeExtensions.Scalar(mediaContainer, "image").Should().Be("localhost/fc-media-web:v20260604-oidc-proper");
|
|
||||||
mediaPvc.Scalar("spec", "storageClassName").Should().Be("longhorn");
|
|
||||||
mediaPvc.Scalar("spec", "resources", "requests", "storage").Should().Be("20Gi");
|
|
||||||
|
|
||||||
mediaDeployment.AllScalars().Should().Contain(new[]
|
|
||||||
{
|
|
||||||
"/volume1/kubernetes/fc-media-transcodes",
|
|
||||||
"/volume1/kubernetes/fc-media-inbox",
|
|
||||||
"/volume1/video",
|
|
||||||
});
|
|
||||||
|
|
||||||
var distributionDeployment = AppDocuments("fc-distribution")
|
|
||||||
.Single(document => document.Kind == "Deployment" && document.Name == "fc-distribution");
|
|
||||||
var distributionContainer = distributionDeployment.MainContainerMappings().Should().ContainSingle().Subject;
|
|
||||||
|
|
||||||
ManifestNodeExtensions.Scalar(distributionContainer, "image").Should().Be("localhost/fc-distribution:v20260604-oidc-root-anon");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void MonitoringProbes_UseHealthzForOidcGatedHosts()
|
|
||||||
{
|
|
||||||
var monitoring = File.ReadAllText(Path.Combine(Inventory.BluejayRoot, "apps", "monitoring", "noc-monitoring.yaml"));
|
|
||||||
|
|
||||||
monitoring.Should().Contain("\"https://dns.iamworkin.lan/healthz\"");
|
|
||||||
monitoring.Should().Contain("\"https://dist.iamworkin.lan/healthz\"");
|
|
||||||
monitoring.Should().Contain("\"https://media.iamworkin.lan/healthz\"");
|
|
||||||
monitoring.Should().NotContain("\"https://dns.iamworkin.lan/\"");
|
|
||||||
monitoring.Should().NotContain("\"https://dist.iamworkin.lan/\"");
|
|
||||||
monitoring.Should().NotContain("\"https://media.iamworkin.lan/\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void DistributionPublicIngress_KeepsGetHeadMethodAllowlist()
|
|
||||||
{
|
|
||||||
var publicIngress = AppDocuments("fc-distribution")
|
|
||||||
.Single(document => document.Kind == "IngressRoute" && document.Name == "fc-distribution-public");
|
|
||||||
var route = publicIngress.MappingSequence("spec", "routes").Should().ContainSingle().Subject;
|
|
||||||
var match = ManifestNodeExtensions.Scalar(route, "match");
|
|
||||||
|
|
||||||
match.Should().Contain("Host(`dist.flowercore.io`)");
|
|
||||||
match.Should().Contain("Method(`GET`)");
|
|
||||||
match.Should().Contain("Method(`HEAD`)");
|
|
||||||
match.Should().NotContain("Method(`POST`)");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void DnsAndMediaIngressRoutes_MatchLiveInternalHosts()
|
|
||||||
{
|
|
||||||
var dnsRoute = AppDocuments("fc-dns")
|
|
||||||
.Single(document => document.Kind == "IngressRoute" && document.Name == "dns-web")
|
|
||||||
.MappingSequence("spec", "routes")
|
|
||||||
.Should()
|
|
||||||
.ContainSingle()
|
|
||||||
.Subject;
|
|
||||||
var mediaRoute = AppDocuments("fc-media")
|
|
||||||
.Single(document => document.Kind == "IngressRoute" && document.Name == "fc-media-web")
|
|
||||||
.MappingSequence("spec", "routes")
|
|
||||||
.Should()
|
|
||||||
.ContainSingle()
|
|
||||||
.Subject;
|
|
||||||
|
|
||||||
ManifestNodeExtensions.Scalar(dnsRoute, "match").Should().Be("Host(`dns.iamworkin.lan`)");
|
|
||||||
ManifestNodeExtensions.Scalar(mediaRoute, "match").Should().Be("Host(`media.iamworkin.lan`)");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<string> ProbeViolations(
|
private static IEnumerable<string> ProbeViolations(
|
||||||
ManifestDocument document,
|
ManifestDocument document,
|
||||||
YamlMappingNode container,
|
YamlMappingNode container,
|
||||||
@@ -1067,25 +838,6 @@ public sealed class FleetManifestLintTests
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? EnvSecretOptional(YamlMappingNode container, string name)
|
|
||||||
{
|
|
||||||
return EnvMapping(container, name) is { } env
|
|
||||||
? ManifestNodeExtensions.Scalar(env, "valueFrom", "secretKeyRef", "optional")
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string? ProbePath(YamlMappingNode container, string probeKey)
|
|
||||||
{
|
|
||||||
return ManifestNodeExtensions.Scalar(container, probeKey, "httpGet", "path");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IReadOnlyList<ManifestDocument> AppDocuments(string app)
|
|
||||||
{
|
|
||||||
return Inventory.Documents
|
|
||||||
.Where(document => document.RelativePath.StartsWith($"{app}/", StringComparison.Ordinal))
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static YamlMappingNode? EnvMapping(YamlMappingNode container, string name)
|
private static YamlMappingNode? EnvMapping(YamlMappingNode container, string name)
|
||||||
{
|
{
|
||||||
return ManifestNodeExtensions.MappingSequence(container, "env")
|
return ManifestNodeExtensions.MappingSequence(container, "env")
|
||||||
@@ -1105,20 +857,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
|
||||||
|
|||||||
Reference in New Issue
Block a user