Compare commits
77 Commits
codex/whc4
...
gx10-gitop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d70d68afa0 | ||
|
|
70ede651fb | ||
|
|
bfa1d011c1 | ||
|
|
f132d04e3f | ||
|
|
bf30cf0503 | ||
|
|
5f3bf05258 | ||
|
|
5725c9b36b | ||
|
|
b8fc8ba208 | ||
|
|
5e7d9338b5 | ||
|
|
e543d4053a | ||
|
|
5ce4f0d1e7 | ||
|
|
4c369cc7ec | ||
|
|
299ce5aeed | ||
|
|
57a1afe159 | ||
|
|
0d71a789c2 | ||
|
|
14d89ba49d | ||
|
|
0eda4362ce | ||
|
|
6f12ace02d | ||
|
|
0c03e53df9 | ||
|
|
62a3e75ddc | ||
|
|
4bbd157c8f | ||
|
|
1969285e4f | ||
|
|
68a5f1ac5d | ||
|
|
f0b122bac7 | ||
|
|
c9538eeeef | ||
|
|
c968e1c4d9 | ||
|
|
bc39da26a1 | ||
|
|
984e3423db | ||
|
|
5d0baa0fdd | ||
|
|
f594d82c65 | ||
|
|
0b7d0fa476 | ||
|
|
500b2484ab | ||
|
|
c0a0341cef | ||
|
|
adafbb41f7 | ||
|
|
09dce583bb | ||
|
|
6d0464ec17 | ||
|
|
3b96a6272a | ||
|
|
061a0d61a8 | ||
|
|
ae6dfe9144 | ||
|
|
28f9ac2ef9 | ||
|
|
a7ba47e307 | ||
|
|
2e8cabcd63 | ||
|
|
3948350ac2 | ||
|
|
ac153248c2 | ||
|
|
9cef99739a | ||
|
|
bd050c3d9b | ||
|
|
a41b22bca4 | ||
|
|
38590d3d5a | ||
|
|
27815cefca | ||
|
|
6e0d33b5b9 | ||
|
|
b015c8a8e1 | ||
|
|
d51e55c78d | ||
|
|
f78e6747b4 | ||
|
|
e543018bdc | ||
|
|
d0c9717d90 | ||
|
|
2c1aa3f0c8 | ||
|
|
aba9d7c995 | ||
|
|
a56e98422f | ||
|
|
27600b8b99 | ||
|
|
9929a91812 | ||
|
|
5af4d9077a | ||
|
|
efa0434b9b | ||
|
|
ad709e2317 | ||
|
|
f636b5092c | ||
|
|
82d9d66f62 | ||
|
|
8b1f8df3dd | ||
|
|
65af283aea | ||
|
|
b7d34da3d6 | ||
|
|
63fde0a593 | ||
|
|
764e4a8f49 | ||
|
|
3bdb9eee81 | ||
|
|
83db2bbe6b | ||
|
|
c32327cdee | ||
|
|
818463562e | ||
|
|
f821c0e661 | ||
|
|
6c519bfd6a | ||
|
|
ee14d3a2d0 |
@@ -101,7 +101,7 @@ curl -sk -X DELETE https://dns.iamworkin.lan/api/v1/servers/<serverId>/zones/iam
|
|||||||
- **StatefulSet PVC drift**: `volumeClaimTemplates` needs explicit `volumeMode: Filesystem` or ArgoCD SSA self-heals forever. See memory `feedback_argocd_statefulset_pvc_drift.md`.
|
- **StatefulSet PVC drift**: `volumeClaimTemplates` needs explicit `volumeMode: Filesystem` or ArgoCD SSA self-heals forever. See memory `feedback_argocd_statefulset_pvc_drift.md`.
|
||||||
- **IngressRoute namespace split**: this RKE2 Traefik install does not allow cross-namespace service refs. Keep the `IngressRoute`, backend `Service`, and TLS secret in the same namespace; if one host is shared across namespaces, duplicate the `Certificate` and move the route next to the destination service.
|
- **IngressRoute namespace split**: this RKE2 Traefik install does not allow cross-namespace service refs. Keep the `IngressRoute`, backend `Service`, and TLS secret in the same namespace; if one host is shared across namespaces, duplicate the `Certificate` and move the route next to the destination service.
|
||||||
- **Public read-only hosts**: if a public host fronts a service that also exposes admin writes internally, add a Traefik route match like `Host(...) && (Method(GET) || Method(HEAD))` on the public edge instead of trusting the app to reject unsafe methods.
|
- **Public read-only hosts**: if a public host fronts a service that also exposes admin writes internally, add a Traefik route match like `Host(...) && (Method(GET) || Method(HEAD))` on the public edge instead of trusting the app to reject unsafe methods.
|
||||||
- **Public read-write allowlist hosts**: if a public host accepts a tightly bounded write surface (e.g. bootstrap-JWT POST), pin the allowlist as `(Method(GET) || Method(HEAD) || Method(POST) || Method(OPTIONS))`. PUT/PATCH/DELETE must still 404 at the route. Track A's `updatecenter.iamworkin.lan` / `updates.iamworkin.lan` are the canonical example. The lint test enforces this invariant.
|
- **Public read-write allowlist hosts**: if a public host accepts a tightly bounded write surface (e.g. bootstrap-JWT POST), pin the allowlist as `(Method(GET) || Method(HEAD) || Method(POST) || Method(OPTIONS))`. PUT/PATCH/DELETE must still 404 at the route. Internal UpdateCenter hosts (`updatecenter.iamworkin.lan` / `updates.iamworkin.lan`) are the canonical example. Public UpdateCenter delivery hosts (`update.flowercore.io` / `updates.flowercore.io`) stay GET/HEAD-only and share-link gated until an explicit operator decision changes that posture.
|
||||||
- **Traefik VIP netpols**: when a `NetworkPolicy` allows `10.0.56.200`, also allow the post-DNAT backend ports (`8443` for TLS plus `8080` or `8000` for HTTP) or Calico will drop the rewritten flow.
|
- **Traefik VIP netpols**: when a `NetworkPolicy` allows `10.0.56.200`, also allow the post-DNAT backend ports (`8443` for TLS plus `8080` or `8000` for HTTP) or Calico will drop the rewritten flow.
|
||||||
- **Auth-safe probes**: services behind API-key or global auth middleware should prefer `tcpSocket` probes unless `/health` is explicitly exempted before the middleware runs.
|
- **Auth-safe probes**: services behind API-key or global auth middleware should prefer `tcpSocket` probes unless `/health` is explicitly exempted before the middleware runs.
|
||||||
- **ArgoCD must use internal Gitea URL**: `http://gitea-clusterip.gitea.svc.cluster.local:3000/bluejay/bluejay-infra.git`, not the external HTTPS URL (step-ca cert isn't trusted by ArgoCD). The `ApplicationSet` and any hand-created `Application` must both use the internal URL.
|
- **ArgoCD must use internal Gitea URL**: `http://gitea-clusterip.gitea.svc.cluster.local:3000/bluejay/bluejay-infra.git`, not the external HTTPS URL (step-ca cert isn't trusted by ArgoCD). The `ApplicationSet` and any hand-created `Application` must both use the internal URL.
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-aistation-web:v20260616-ai6-gx10-websurface-a8a3e9d-2f9f89e",
|
"image": "localhost/fc-aistation-web:v20260619-aistation-eb9d513",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"livenessProbe": {
|
"livenessProbe": {
|
||||||
"failureThreshold": 3,
|
"failureThreshold": 3,
|
||||||
|
|||||||
61
apps-gx10/fc-apple-mdm/README.md
Normal file
61
apps-gx10/fc-apple-mdm/README.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# FlowerCore Apple MDM on GX10
|
||||||
|
|
||||||
|
This directory deploys the NanoHUB `v0.2.0` substrate for Apple MDM protocol
|
||||||
|
traffic at `https://mdm.iamworkin.lan`.
|
||||||
|
|
||||||
|
## Runtime
|
||||||
|
|
||||||
|
- Namespace: `fc-apple-mdm`
|
||||||
|
- Image: `localhost/fc-apple-mdm-nanohub:v0.2.0-20260617`
|
||||||
|
- Upstream digest: `ghcr.io/micromdm/nanohub:latest@sha256:e36a50db2dc3d2bf736645e58712f622c04b05b28487390981905ef4d0be5fbd`
|
||||||
|
- Persistent state: `fc-apple-mdm-data` on `local-path`, mounted at `/var/lib/nanohub`
|
||||||
|
- File backend DSN: `/var/lib/nanohub/db`
|
||||||
|
- Required secret: `Secret/fc-apple-mdm-runtime`, key `NANOHUB_API_KEY`
|
||||||
|
- Optional later bridge secret: `NANOHUB_WEBHOOK_URL`
|
||||||
|
- Required CA mount: `ConfigMap/fc-apple-mdm-root-ca`, key `root_ca.crt`
|
||||||
|
- SCEP backend: noc1 systemd service `step-ca-apple-mdm-scep`, forwarded through
|
||||||
|
selectorless `Service/fc-apple-mdm-scep` and `EndpointSlice/fc-apple-mdm-scep-noc1`
|
||||||
|
to `10.0.56.10:9080`
|
||||||
|
|
||||||
|
NanoHUB API authentication is HTTP Basic with username `nanohub` and password
|
||||||
|
from `NANOHUB_API_KEY`.
|
||||||
|
|
||||||
|
## Public Surface
|
||||||
|
|
||||||
|
The Traefik route intentionally exposes only:
|
||||||
|
|
||||||
|
- `/version`
|
||||||
|
- `/mdm`
|
||||||
|
- `/checkin`
|
||||||
|
- `/scep`
|
||||||
|
|
||||||
|
NanoHUB APIs under `/api/v1/*` stay cluster-internal for MDM-N1. The
|
||||||
|
DeviceManagement bridge can use the ClusterIP service directly once its NanoHUB
|
||||||
|
client lane lands.
|
||||||
|
|
||||||
|
SCEP is backed by the dedicated Apple-MDM-specific RSA step-ca hierarchy on
|
||||||
|
noc1, not by the IAmWorkin ACME CA. The live profile URL is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://mdm.iamworkin.lan/scep/apple-mdm-scep
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not point `APPLE_MDM_SCEP_URL` at a placeholder URL or at the ECDSA
|
||||||
|
IAmWorkin ACME CA; Smallstep SCEP requires an RSA intermediate/decrypter path.
|
||||||
|
|
||||||
|
## Deployment Notes
|
||||||
|
|
||||||
|
1. Create or refresh the runtime Kubernetes Secret from the 1Password item
|
||||||
|
`FlowerCore Apple MDM Runtime` before sync. GX10 does not yet depend on the
|
||||||
|
1Password operator for this workload.
|
||||||
|
2. Import `localhost/fc-apple-mdm-nanohub:v0.2.0-20260617` into GX10 containerd
|
||||||
|
before ArgoCD syncs. The deployment uses `imagePullPolicy: Never`.
|
||||||
|
3. Ensure `mdm.iamworkin.lan` resolves to the GX10 Traefik VIP `10.0.57.202`
|
||||||
|
before cert-manager requests `Certificate/fc-apple-mdm-tls`.
|
||||||
|
4. Prove `https://mdm.iamworkin.lan/version` after ArgoCD converges.
|
||||||
|
5. Prove SCEP CA publication with
|
||||||
|
`curl -sk -o /dev/null -w '%{http_code} %{size_download}\n' 'https://mdm.iamworkin.lan/scep/apple-mdm-scep?operation=GetCACert'`.
|
||||||
|
|
||||||
|
This lane does not create an APNs MDM push certificate, enrollment profile,
|
||||||
|
managed Wi-Fi payload, managed app install, or supervised iPad enrollment. Those
|
||||||
|
remain MDM-N2 through MDM-N8.
|
||||||
322
apps-gx10/fc-apple-mdm/fc-apple-mdm.yaml
Normal file
322
apps-gx10/fc-apple-mdm/fc-apple-mdm.yaml
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
# FlowerCore Apple MDM NanoHUB workload for the GX10 cluster.
|
||||||
|
# Secret values are copied into Kubernetes Secrets out of band until the
|
||||||
|
# 1Password operator exists on GX10; never commit secret data here.
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: fc-apple-mdm
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: fc-apple-mdm-root-ca
|
||||||
|
namespace: fc-apple-mdm
|
||||||
|
data:
|
||||||
|
root_ca.crt: |
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBxDCCAWqgAwIBAgIRAPY357G6ow6zMAL5+4bS2kkwCgYIKoZIzj0EAwIwQDEa
|
||||||
|
MBgGA1UEChMRSUFtV29ya2luIEFDTUUgQ0ExIjAgBgNVBAMTGUlBbVdvcmtpbiBB
|
||||||
|
Q01FIENBIFJvb3QgQ0EwHhcNMjYwMzA4MTgwNzExWhcNMzYwMzA1MTgwNzExWjBA
|
||||||
|
MRowGAYDVQQKExFJQW1Xb3JraW4gQUNNRSBDQTEiMCAGA1UEAxMZSUFtV29ya2lu
|
||||||
|
IEFDTUUgQ0EgUm9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJ2n04X1
|
||||||
|
JZo5Zdq/i1Idv8+fqwZyAzBh7whbqj0SWsJL8UWRabCMqYCs7+dXO0xRSzqkwFDL
|
||||||
|
x+vooOai8RgRNhajRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/
|
||||||
|
AgEBMB0GA1UdDgQWBBRnuPPQR6iM/H6vOluiU3Sygayz8jAKBggqhkjOPQQDAgNI
|
||||||
|
ADBFAiEArQK9dYPGmAZsdYnjziuFVVE5NKZUcceYvGfGC+tLXUsCIAudF2zJrCRq
|
||||||
|
3mK50ZZET/fwTkJwiEF4824mjP8p1CKM
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: fc-apple-mdm-data
|
||||||
|
namespace: fc-apple-mdm
|
||||||
|
labels:
|
||||||
|
app: fc-apple-mdm
|
||||||
|
app.kubernetes.io/name: fc-apple-mdm
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: local-path
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 2Gi
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: fc-apple-mdm
|
||||||
|
namespace: fc-apple-mdm
|
||||||
|
labels:
|
||||||
|
app: fc-apple-mdm
|
||||||
|
app.kubernetes.io/name: fc-apple-mdm
|
||||||
|
app.kubernetes.io/component: mdm
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: fc-apple-mdm
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: fc-apple-mdm
|
||||||
|
app.kubernetes.io/name: fc-apple-mdm
|
||||||
|
app.kubernetes.io/component: mdm
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
annotations:
|
||||||
|
fc.flowercore.io/healthz-anon: "true"
|
||||||
|
fc.flowercore.io/probe-path: "/version"
|
||||||
|
flowercore.io/audit-trace-id: "apple-mdm-nanohub-runtime-trace"
|
||||||
|
flowercore.io/root-ca-sha256: "a9120c88fa3ec735d790aa4cfeb61ac2946730338969015bebaccc08fe10535e"
|
||||||
|
prometheus.io/scrape: "false"
|
||||||
|
spec:
|
||||||
|
enableServiceLinks: false
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1654
|
||||||
|
runAsGroup: 1654
|
||||||
|
fsGroup: 1654
|
||||||
|
fsGroupChangePolicy: OnRootMismatch
|
||||||
|
containers:
|
||||||
|
- name: nanohub
|
||||||
|
image: localhost/fc-apple-mdm-nanohub:v0.2.0-20260617
|
||||||
|
imagePullPolicy: Never
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 9004
|
||||||
|
protocol: TCP
|
||||||
|
env:
|
||||||
|
- name: HOME
|
||||||
|
value: "/var/lib/nanohub"
|
||||||
|
- name: NANOHUB_LISTEN
|
||||||
|
value: ":9004"
|
||||||
|
- name: NANOHUB_STORAGE
|
||||||
|
value: "file"
|
||||||
|
- name: NANOHUB_STORAGE_DSN
|
||||||
|
value: "/var/lib/nanohub/db"
|
||||||
|
- name: NANOHUB_CHECKIN
|
||||||
|
value: "true"
|
||||||
|
- name: NANOHUB_CA
|
||||||
|
value: "/etc/nanohub/ca/root_ca.crt"
|
||||||
|
- name: NANOHUB_API_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: fc-apple-mdm-runtime
|
||||||
|
key: NANOHUB_API_KEY
|
||||||
|
- name: NANOHUB_WEBHOOK_URL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: fc-apple-mdm-runtime
|
||||||
|
key: NANOHUB_WEBHOOK_URL
|
||||||
|
optional: true
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /version
|
||||||
|
port: 9004
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
failureThreshold: 30
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /version
|
||||||
|
port: 9004
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 9004
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
failureThreshold: 3
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1654
|
||||||
|
runAsGroup: 1654
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /var/lib/nanohub
|
||||||
|
- name: tmp
|
||||||
|
mountPath: /tmp
|
||||||
|
- name: root-ca
|
||||||
|
mountPath: /etc/nanohub/ca
|
||||||
|
readOnly: true
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: fc-apple-mdm-data
|
||||||
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
|
- name: root-ca
|
||||||
|
configMap:
|
||||||
|
name: fc-apple-mdm-root-ca
|
||||||
|
items:
|
||||||
|
- key: root_ca.crt
|
||||||
|
path: root_ca.crt
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: fc-apple-mdm
|
||||||
|
namespace: fc-apple-mdm
|
||||||
|
labels:
|
||||||
|
app: fc-apple-mdm
|
||||||
|
app.kubernetes.io/name: fc-apple-mdm
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: fc-apple-mdm
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 9004
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: fc-apple-mdm-scep
|
||||||
|
namespace: fc-apple-mdm
|
||||||
|
labels:
|
||||||
|
app: fc-apple-mdm-scep
|
||||||
|
app.kubernetes.io/name: fc-apple-mdm-scep
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 9080
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
|
apiVersion: discovery.k8s.io/v1
|
||||||
|
kind: EndpointSlice
|
||||||
|
metadata:
|
||||||
|
name: fc-apple-mdm-scep-noc1
|
||||||
|
namespace: fc-apple-mdm
|
||||||
|
labels:
|
||||||
|
kubernetes.io/service-name: fc-apple-mdm-scep
|
||||||
|
app.kubernetes.io/name: fc-apple-mdm-scep
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
addressType: IPv4
|
||||||
|
endpoints:
|
||||||
|
- addresses:
|
||||||
|
- 10.0.56.10
|
||||||
|
conditions:
|
||||||
|
ready: true
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 9080
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: fc-apple-mdm-tls
|
||||||
|
namespace: fc-apple-mdm
|
||||||
|
annotations:
|
||||||
|
flowercore.io/dns-preflight: "mdm.iamworkin.lan must resolve to 10.0.57.202 before ACME sync"
|
||||||
|
spec:
|
||||||
|
secretName: fc-apple-mdm-tls
|
||||||
|
issuerRef:
|
||||||
|
name: step-ca-acme
|
||||||
|
kind: ClusterIssuer
|
||||||
|
dnsNames:
|
||||||
|
- mdm.iamworkin.lan
|
||||||
|
duration: 720h
|
||||||
|
renewBefore: 240h
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: fc-apple-mdm
|
||||||
|
namespace: fc-apple-mdm
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`mdm.iamworkin.lan`) && PathPrefix(`/scep`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: fc-apple-mdm-scep
|
||||||
|
port: 80
|
||||||
|
- match: Host(`mdm.iamworkin.lan`) && (PathPrefix(`/mdm`) || PathPrefix(`/checkin`) || PathPrefix(`/version`))
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: fc-apple-mdm
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
secretName: fc-apple-mdm-tls
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: fc-apple-mdm-netpol
|
||||||
|
namespace: fc-apple-mdm
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: fc-apple-mdm
|
||||||
|
policyTypes:
|
||||||
|
- Ingress
|
||||||
|
- Egress
|
||||||
|
ingress:
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: traefik-system
|
||||||
|
ports:
|
||||||
|
- port: 9004
|
||||||
|
protocol: TCP
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: fc-devicemgmt
|
||||||
|
ports:
|
||||||
|
- port: 9004
|
||||||
|
protocol: TCP
|
||||||
|
egress:
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: kube-system
|
||||||
|
ports:
|
||||||
|
- port: 53
|
||||||
|
protocol: UDP
|
||||||
|
- port: 53
|
||||||
|
protocol: TCP
|
||||||
|
- to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 0.0.0.0/0
|
||||||
|
ports:
|
||||||
|
- port: 443
|
||||||
|
protocol: TCP
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: fc-devicemgmt
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
protocol: TCP
|
||||||
|
- port: 8080
|
||||||
|
protocol: TCP
|
||||||
6
apps-gx10/fc-apple-mdm/kustomization.yaml
Normal file
6
apps-gx10/fc-apple-mdm/kustomization.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# ArgoCD discovers apps-gx10/* directories on the GX10 GitOps branch.
|
||||||
|
# This kustomization is for local previews and single-app validation.
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- fc-apple-mdm.yaml
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-chat-web:v20260617-chatfix-54fd549",
|
"image": "localhost/fc-chat-web:v20260619-sec3-5a8859b",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"livenessProbe": {
|
"livenessProbe": {
|
||||||
"failureThreshold": 3,
|
"failureThreshold": 3,
|
||||||
|
|||||||
69
apps-gx10/fc-devicemgmt/README.md
Normal file
69
apps-gx10/fc-devicemgmt/README.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# FlowerCore DeviceManagement on GX10
|
||||||
|
|
||||||
|
This adopted GX10 app hosts `FlowerCore.DeviceManagement.Web` at
|
||||||
|
`https://devices.iamworkin.lan`. Agent-only REST/SignalR callbacks can use
|
||||||
|
`https://devices-agent.iamworkin.lan`, which is a separate Traefik router that
|
||||||
|
requires a TLS client certificate and forwards the presented certificate to the
|
||||||
|
app. Traefik v3.6 currently forwards raw base64 DER in
|
||||||
|
`X-Forwarded-Tls-Client-Cert`; the app also accepts URL-escaped PEM for
|
||||||
|
compatibility with older/alternate Traefik shapes.
|
||||||
|
|
||||||
|
## Apple MDM Runtime Contract
|
||||||
|
|
||||||
|
Apple MDM is enabled in NanoHUB mode, but enrollment remains unavailable until
|
||||||
|
the runtime secret contains real Apple-side material. Do not use placeholder
|
||||||
|
values to clear readiness checks.
|
||||||
|
|
||||||
|
`Secret/fc-devicemgmt-runtime` supports these Apple MDM keys:
|
||||||
|
|
||||||
|
| Key | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| `DEVICE_MANAGEMENT_OPERATOR_API_KEY` | Required operator API key for authenticated REST/MCP write operations, including Android command queueing. |
|
||||||
|
| `DEVICE_MANAGEMENT_ADMIN_API_KEY` | Required admin API key for privileged DeviceManagement operations. |
|
||||||
|
| `DEVICE_MANAGEMENT_AGENT_API_KEY` | Required scoped agent credential for REST agent callbacks when TLS terminates before Kestrel; maps to `Auth:AgentApiKey` and `FlowerCore:Auth:AgentApiKey`. |
|
||||||
|
| `DEVICE_MANAGEMENT_ENROLLMENT_CA_CERTIFICATE_PEM` | Optional persistent enrollment CA certificate PEM; maps to `FlowerCore:DeviceManagement:EnrollmentCertificateAuthorityCertificatePem`. Required before ingress can verify agent client-cert chains. |
|
||||||
|
| `DEVICE_MANAGEMENT_ENROLLMENT_CA_PRIVATE_KEY_PEM` | Optional private key PEM matching the persistent enrollment CA certificate; maps to `FlowerCore:DeviceManagement:EnrollmentCertificateAuthorityPrivateKeyPem`. |
|
||||||
|
| `NANOHUB_API_KEY` | NanoHUB API password for HTTP Basic user `nanohub`. |
|
||||||
|
| `APPLE_MDM_APNS_TOPIC` | MDM APNs topic returned after uploading the Apple MDM push certificate to NanoHUB/NanoMDM. |
|
||||||
|
| `APPLE_MDM_SCEP_URL` | Live SCEP URL included in the enrollment profile. |
|
||||||
|
| `APPLE_MDM_SCEP_CHALLENGE` | SCEP challenge shared with the SCEP provisioner. |
|
||||||
|
| `APPLE_MDM_PROFILE_SIGNING_CERTIFICATE_PEM` | PEM certificate used to CMS-sign `.mobileconfig` profiles. |
|
||||||
|
| `APPLE_MDM_PROFILE_SIGNING_PRIVATE_KEY_PEM` | PEM private key matching the profile-signing certificate. |
|
||||||
|
| `APPLE_MDM_REQUIRE_MANAGED_WIFI_PAYLOAD` | Set to `true` only when Wi-Fi payload delivery should gate enrollment readiness. |
|
||||||
|
| `APPLE_MDM_MANAGED_WIFI_SSID` | Managed Wi-Fi SSID for the iPad profile. |
|
||||||
|
| `APPLE_MDM_MANAGED_WIFI_PASSWORD` | Managed Wi-Fi password when the network is not open. |
|
||||||
|
|
||||||
|
Non-secret profile constants stay in GitOps: NanoHUB base URL, MDM server URL,
|
||||||
|
check-in URL, organization/display names, the HTTPS trust anchor certificate,
|
||||||
|
managed Wi-Fi encryption type, auto-join, and MAC-randomization disablement.
|
||||||
|
|
||||||
|
DeviceManagement auth is enabled on GX10. The deployment maps
|
||||||
|
`DEVICE_MANAGEMENT_OPERATOR_API_KEY` to both `Auth__ApiKey` and
|
||||||
|
`FlowerCore__Auth__ApiKey`; the unprefixed key keeps the MCP API key post-config
|
||||||
|
path aligned with REST auth. Agent heartbeat, inventory, command poll, app-catalog,
|
||||||
|
and command-result callbacks use the agent-specific authorization boundary: the
|
||||||
|
server validates a direct device client certificate when Kestrel receives one,
|
||||||
|
validates Traefik-forwarded client certificates only on
|
||||||
|
`devices-agent.iamworkin.lan`, and also accepts only the scoped
|
||||||
|
`DEVICE_MANAGEMENT_AGENT_API_KEY` via `Authorization: Bearer` or
|
||||||
|
`X-Agent-Api-Key` as the fallback path. Operator write endpoints must use
|
||||||
|
`X-Api-Key`.
|
||||||
|
|
||||||
|
The agent-only Traefik route currently uses `RequireAnyClientCert`; the
|
||||||
|
application remains the authorization boundary by matching the forwarded client
|
||||||
|
certificate thumbprint to the enrolled device record. Once
|
||||||
|
`DEVICE_MANAGEMENT_ENROLLMENT_CA_CERTIFICATE_PEM` and
|
||||||
|
`DEVICE_MANAGEMENT_ENROLLMENT_CA_PRIVATE_KEY_PEM` are present and newly enrolled
|
||||||
|
agents prove they chain to that CA, create the matching Traefik CA secret and
|
||||||
|
switch this TLSOption to `RequireAndVerifyClientCert`.
|
||||||
|
|
||||||
|
## Readiness Check
|
||||||
|
|
||||||
|
After changing the runtime secret and letting the pod roll, verify:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sk https://devices.iamworkin.lan/api/v1/apple-mdm/enrollment-profile/status
|
||||||
|
```
|
||||||
|
|
||||||
|
Configurator enrollment must wait until this status reports `available=true`
|
||||||
|
and an empty `missingRequirements` array.
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "cert-manager.io/v1",
|
||||||
|
"kind": "Certificate",
|
||||||
|
"metadata": {
|
||||||
|
"name": "fc-devicemgmt-agent-tls",
|
||||||
|
"namespace": "fc-devicemgmt"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"dnsNames": [
|
||||||
|
"devices-agent.iamworkin.lan"
|
||||||
|
],
|
||||||
|
"issuerRef": {
|
||||||
|
"kind": "ClusterIssuer",
|
||||||
|
"name": "step-ca-acme"
|
||||||
|
},
|
||||||
|
"secretName": "fc-devicemgmt-agent-tls"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -97,12 +97,251 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__Auth__Enabled",
|
||||||
|
"value": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Auth__ApiKey",
|
||||||
|
"valueFrom": {
|
||||||
|
"secretKeyRef": {
|
||||||
|
"key": "DEVICE_MANAGEMENT_OPERATOR_API_KEY",
|
||||||
|
"name": "fc-devicemgmt-runtime"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__Auth__ApiKey",
|
||||||
|
"valueFrom": {
|
||||||
|
"secretKeyRef": {
|
||||||
|
"key": "DEVICE_MANAGEMENT_OPERATOR_API_KEY",
|
||||||
|
"name": "fc-devicemgmt-runtime"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Auth__AdminApiKey",
|
||||||
|
"valueFrom": {
|
||||||
|
"secretKeyRef": {
|
||||||
|
"key": "DEVICE_MANAGEMENT_ADMIN_API_KEY",
|
||||||
|
"name": "fc-devicemgmt-runtime"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__Auth__AdminApiKey",
|
||||||
|
"valueFrom": {
|
||||||
|
"secretKeyRef": {
|
||||||
|
"key": "DEVICE_MANAGEMENT_ADMIN_API_KEY",
|
||||||
|
"name": "fc-devicemgmt-runtime"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Auth__AgentApiKey",
|
||||||
|
"valueFrom": {
|
||||||
|
"secretKeyRef": {
|
||||||
|
"key": "DEVICE_MANAGEMENT_AGENT_API_KEY",
|
||||||
|
"name": "fc-devicemgmt-runtime"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__Auth__AgentApiKey",
|
||||||
|
"valueFrom": {
|
||||||
|
"secretKeyRef": {
|
||||||
|
"key": "DEVICE_MANAGEMENT_AGENT_API_KEY",
|
||||||
|
"name": "fc-devicemgmt-runtime"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__EnrollmentCertificateAuthorityCertificatePem",
|
||||||
|
"valueFrom": {
|
||||||
|
"secretKeyRef": {
|
||||||
|
"key": "DEVICE_MANAGEMENT_ENROLLMENT_CA_CERTIFICATE_PEM",
|
||||||
|
"name": "fc-devicemgmt-runtime",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__EnrollmentCertificateAuthorityPrivateKeyPem",
|
||||||
|
"valueFrom": {
|
||||||
|
"secretKeyRef": {
|
||||||
|
"key": "DEVICE_MANAGEMENT_ENROLLMENT_CA_PRIVATE_KEY_PEM",
|
||||||
|
"name": "fc-devicemgmt-runtime",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AgentMtls__ForwardedCertificateHosts__0",
|
||||||
|
"value": "devices-agent.iamworkin.lan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AgentMtls__ForwardedCertificateHeader",
|
||||||
|
"value": "X-Forwarded-Tls-Client-Cert"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "FlowerCore__EventBus__Redis__Configuration",
|
"name": "FlowerCore__EventBus__Redis__Configuration",
|
||||||
"value": "redis.fc-redis.svc:6379"
|
"value": "redis.fc-redis.svc:6379"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__Enabled",
|
||||||
|
"value": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__GatewayMode",
|
||||||
|
"value": "nanohub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__NanoHubBaseUrl",
|
||||||
|
"value": "http://fc-apple-mdm.fc-apple-mdm.svc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__NanoHubApiUserName",
|
||||||
|
"value": "nanohub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__NanoHubNanoMdmApiPath",
|
||||||
|
"value": "/api/v1/nanomdm/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__EnrollmentProfileDownloadUrl",
|
||||||
|
"value": "https://devices.iamworkin.lan/api/v1/apple-mdm/enrollment-profile.mobileconfig"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__MdmServerUrl",
|
||||||
|
"value": "https://mdm.iamworkin.lan/mdm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__MdmCheckInUrl",
|
||||||
|
"value": "https://mdm.iamworkin.lan/checkin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__Organization",
|
||||||
|
"value": "FlowerCore"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__EnrollmentDisplayName",
|
||||||
|
"value": "FlowerCore Apple MDM"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__ScepName",
|
||||||
|
"value": "FlowerCore Apple MDM Device Identity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__TrustAnchorDisplayName",
|
||||||
|
"value": "IAmWorkin ACME CA Root CA"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__TrustAnchorCertificatePem",
|
||||||
|
"value": "-----BEGIN CERTIFICATE-----\nMIIBxDCCAWqgAwIBAgIRAPY357G6ow6zMAL5+4bS2kkwCgYIKoZIzj0EAwIwQDEa\nMBgGA1UEChMRSUFtV29ya2luIEFDTUUgQ0ExIjAgBgNVBAMTGUlBbVdvcmtpbiBB\nQ01FIENBIFJvb3QgQ0EwHhcNMjYwMzA4MTgwNzExWhcNMzYwMzA1MTgwNzExWjBA\nMRowGAYDVQQKExFJQW1Xb3JraW4gQUNNRSBDQTEiMCAGA1UEAxMZSUFtV29ya2lu\nIEFDTUUgQ0EgUm9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJ2n04X1\nJZo5Zdq/i1Idv8+fqwZyAzBh7whbqj0SWsJL8UWRabCMqYCs7+dXO0xRSzqkwFDL\nx+vooOai8RgRNhajRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/\nAgEBMB0GA1UdDgQWBBRnuPPQR6iM/H6vOluiU3Sygayz8jAKBggqhkjOPQQDAgNI\nADBFAiEArQK9dYPGmAZsdYnjziuFVVE5NKZUcceYvGfGC+tLXUsCIAudF2zJrCRq\n3mK50ZZET/fwTkJwiEF4824mjP8p1CKM\n-----END CERTIFICATE-----"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__NanoHubApiKey",
|
||||||
|
"valueFrom": {
|
||||||
|
"secretKeyRef": {
|
||||||
|
"key": "NANOHUB_API_KEY",
|
||||||
|
"name": "fc-devicemgmt-runtime"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__ApnsTopic",
|
||||||
|
"valueFrom": {
|
||||||
|
"secretKeyRef": {
|
||||||
|
"key": "APPLE_MDM_APNS_TOPIC",
|
||||||
|
"name": "fc-devicemgmt-runtime",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__ScepUrl",
|
||||||
|
"valueFrom": {
|
||||||
|
"secretKeyRef": {
|
||||||
|
"key": "APPLE_MDM_SCEP_URL",
|
||||||
|
"name": "fc-devicemgmt-runtime",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__ScepChallenge",
|
||||||
|
"valueFrom": {
|
||||||
|
"secretKeyRef": {
|
||||||
|
"key": "APPLE_MDM_SCEP_CHALLENGE",
|
||||||
|
"name": "fc-devicemgmt-runtime",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__ProfileSigningCertificatePem",
|
||||||
|
"valueFrom": {
|
||||||
|
"secretKeyRef": {
|
||||||
|
"key": "APPLE_MDM_PROFILE_SIGNING_CERTIFICATE_PEM",
|
||||||
|
"name": "fc-devicemgmt-runtime",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__ProfileSigningPrivateKeyPem",
|
||||||
|
"valueFrom": {
|
||||||
|
"secretKeyRef": {
|
||||||
|
"key": "APPLE_MDM_PROFILE_SIGNING_PRIVATE_KEY_PEM",
|
||||||
|
"name": "fc-devicemgmt-runtime",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__RequireManagedWifiPayload",
|
||||||
|
"valueFrom": {
|
||||||
|
"secretKeyRef": {
|
||||||
|
"key": "APPLE_MDM_REQUIRE_MANAGED_WIFI_PAYLOAD",
|
||||||
|
"name": "fc-devicemgmt-runtime",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__ManagedWifiSsid",
|
||||||
|
"valueFrom": {
|
||||||
|
"secretKeyRef": {
|
||||||
|
"key": "APPLE_MDM_MANAGED_WIFI_SSID",
|
||||||
|
"name": "fc-devicemgmt-runtime",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__ManagedWifiPassword",
|
||||||
|
"valueFrom": {
|
||||||
|
"secretKeyRef": {
|
||||||
|
"key": "APPLE_MDM_MANAGED_WIFI_PASSWORD",
|
||||||
|
"name": "fc-devicemgmt-runtime",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__ManagedWifiEncryptionType",
|
||||||
|
"value": "WPA2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__ManagedWifiAutoJoin",
|
||||||
|
"value": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__DeviceManagement__AppleMdm__ManagedWifiDisableAssociationMacRandomization",
|
||||||
|
"value": "true"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-devicemgmt-web:v20260617-an13-b9c79c4",
|
"image": "localhost/fc-devicemgmt-web:v20260619-enrollca-c54623d",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"livenessProbe": {
|
"livenessProbe": {
|
||||||
"failureThreshold": 3,
|
"failureThreshold": 3,
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "traefik.io/v1alpha1",
|
||||||
|
"kind": "IngressRoute",
|
||||||
|
"metadata": {
|
||||||
|
"name": "devicemgmt-agent-mtls",
|
||||||
|
"namespace": "fc-devicemgmt"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"entryPoints": [
|
||||||
|
"websecure"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"kind": "Rule",
|
||||||
|
"match": "Host(`devices-agent.iamworkin.lan`)",
|
||||||
|
"middlewares": [
|
||||||
|
{
|
||||||
|
"name": "devicemgmt-agent-pass-client-cert",
|
||||||
|
"namespace": "fc-devicemgmt"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"name": "fc-devicemgmt-web",
|
||||||
|
"port": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tls": {
|
||||||
|
"options": {
|
||||||
|
"name": "devicemgmt-agent-mtls",
|
||||||
|
"namespace": "fc-devicemgmt"
|
||||||
|
},
|
||||||
|
"secretName": "fc-devicemgmt-agent-tls"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "traefik.io/v1alpha1",
|
||||||
|
"kind": "Middleware",
|
||||||
|
"metadata": {
|
||||||
|
"name": "devicemgmt-agent-pass-client-cert",
|
||||||
|
"namespace": "fc-devicemgmt"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"passTLSClientCert": {
|
||||||
|
"pem": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Secret",
|
||||||
|
"metadata": {
|
||||||
|
"name": "devicemgmt-agent-client-ca",
|
||||||
|
"namespace": "fc-devicemgmt",
|
||||||
|
"labels": {
|
||||||
|
"app.kubernetes.io/name": "fc-devicemgmt-web",
|
||||||
|
"app.kubernetes.io/component": "agent-mtls",
|
||||||
|
"app.kubernetes.io/part-of": "flowercore",
|
||||||
|
"app.kubernetes.io/managed-by": "argocd",
|
||||||
|
"flowercore.io/tenant-id": "system",
|
||||||
|
"flowercore.io/created-by": "bluejay-infra"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "Opaque",
|
||||||
|
"data": {
|
||||||
|
"ca.crt": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUYyRENDQThDZ0F3SUJBZ0lVRTFhTVFrRXFmMzJMaVpwTHdudEd6NSsrWnBRd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2NqRUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQW9NQ2tac2IzZGxja052Y21VeEdqQVlCZ05WQkFzTQpFVVJsZG1salpTQk5ZVzVoWjJWdFpXNTBNVEl3TUFZRFZRUUREQ2xHYkc5M1pYSkRiM0psSUVSbGRtbGpaVTFoCmJtRm5aVzFsYm5RZ1JXNXliMnhzYldWdWRDQkRRVEFlRncweU5qQTJNVGt4TWpBM01UWmFGdzB6TmpBMk1UWXgKTWpBM01UWmFNSEl4Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRS0RBcEdiRzkzWlhKRGIzSmxNUm93R0FZRApWUVFMREJGRVpYWnBZMlVnVFdGdVlXZGxiV1Z1ZERFeU1EQUdBMVVFQXd3cFJteHZkMlZ5UTI5eVpTQkVaWFpwClkyVk5ZVzVoWjJWdFpXNTBJRVZ1Y205c2JHMWxiblFnUTBFd2dnSWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUMKRHdBd2dnSUtBb0lDQVFDNGJLdWxXODV1alBFUmhGa2JZUmtDdHBmdWIrbnVvaHlPaW5lbWRXRERhQUpYQkVPVAoxNlUwc1dFODc0RmdCYzN3elYzQ0FUc3BZMW9VcGRyUUliQUZzWkN2VGZHSlhydmtJZm1yR0tmbGhZMHdZTTRKCm9OUFM2K1hYRlFWZVpzc3AwUnk1YWdLM3dUdE1uTFNuUkhzNm5VWnhIa2s1MzdaNER5NTAvT0xma2dYWnFyRm0KNStLaVJocHNTRytuUm12VmsrSW5yUmg1WS9HeENGN1ZWY2FJcStJRXVxZTIzc3ZMck8vZmJnb3k5MWlBMi9NUApWRWRuUFZ1cnAreFdONkswckZLZmE0QmU4RVhpRXZLK20reUttMEZTUEZ6ZFBON0xFWWhnRVJwYkp0S3RNQXJaCnVJc2FmeVl6M0c1REtBdllodlRyN3VCVnFOK1JQREtuWXNtcmdMcTQ0bXpESjkxcEMzckxOM0lHbXlVbloyZmwKWUc1UFIyNDBFOENOTVo0aHV1b3FZT3puYjJRUndPb1pmbG9rZDBISnJwVm4yUjNIVEpCL3ZacFMxbVlyNUMyTApBdTk2aHdvd0QvR1FlNzlyekRpa1lDempiQTFEZHFkeHZUb3g3Y1lRRUgzVUFZMEFGenpyUm5ISWRGYVgvZ2pyCm5GQy9od2RtQ3VZbm5KQ1Rpb3kzcVJjUFBPTTc4UzJrcGNJdy9MdjYrMDNlNFRUWjI2UVZIV3BQaUtNeHZiVjYKbk4xUUJBNGRjVUZtdzZXWFBIa0pFNnVVcHl5bEI1RzlmNkh6Rkt2TDhOZHNXRjA4WUc5SHc5d1lJTnpzbll3YQpDWkZJUmttenpQVERQZUZUUjUweVErWVRYY2FwZUpsWHQ4VjJRdzlQd0tWL0Rsa09QVE5YYkxDOTN3SURBUUFCCm8yWXdaREFkQmdOVkhRNEVGZ1FVcWgyOVpvUU9aNXRLY05hOEJjUnJISUthTHlnd0h3WURWUjBqQkJnd0ZvQVUKcWgyOVpvUU9aNXRLY05hOEJjUnJISUthTHlnd0VnWURWUjBUQVFIL0JBZ3dCZ0VCL3dJQkFEQU9CZ05WSFE4QgpBZjhFQkFNQ0FRWXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnSUJBS1dsbStkQ1BLZ2lDVUFrL3Zlb1RCV1dxMWFxClhJOC81WG9DQ2owU3Q0ZHdhbXdHVUxhU0J0cnZhNFRJMkY0UmpBU25uRmFIMkhlY3laWm5CZW4wZHUrUitDMzEKdmZkVHIrVEQ4QUlpQkEyZm13L1hvTkJwTEsrKzBNL2xoNjJ3ai80KzBlVGVJSHg4TENDazRxUWxUYUlrNld3bgpuK0JSL2F3ZGhRM2RMZzAvM1kzL2V2Q3FQS1B1OTQrc1d5YkpuMlNUb2l1Y1RhL0ZvbnBIWjdZSU9SM3N4b1JwClh1V2JyS3ZUMGFmL1pVeXVpeTJFd0Y1dGU4N25BMkp5OGVsdzFTeGMrM0RiYlc3M3JJMFhZTGxHa0M1NWhBeFUKa2ZYSE5EaXNvV1BoN1pBV09lVFk0OElPZGgvb1IraW9mbkdiYkFvekh0ZmtYOXdUN3V2RlpYS0xTbHM1SEg4dQpZcDRwazdZam9kdmFTbys5SDhua3lYOGxYNmI2RCs0Mi9xTkVVN1UxYjQrdHcxQ0QyVmlRRUFpMHdLbHgzRFZVCnluMm13Zk9SZjQzT1hVUjIzZHh2UXhEWDR0RUNKUVpxTWxJaFZsRmN4TWN6dzRNcTZiTVZTaVB0ZzZtcml0eEsKOUUrb3JsRnNQRm9GdlphSTlzVGUxQWpkZWlUbjhxdUgyUFZqZ3lEa2E0bmt1UG1JS0FIbDVWSWZtWnVjTndTaApxMVE0REo5OFVCQStlTkpuZ1RBYVlXY1BIa2R4U0xKV3EwVzU1NGFrZUZaUW5qYXg3NFZoQWtxdExEZlhnNWw1ClNUeTcxRDNDNnl4SUpyVmZzV1JLS3dDY2dOOWpWR2dNMHBXMVNRaGZmRDdwK1hNRHZxRDNiejQ5S09XM0VGVzAKcG9vaGY3UGl5eU9TYVh3UgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
16
apps-gx10/fc-devicemgmt/tlsoption-devicemgmt-agent-mtls.json
Normal file
16
apps-gx10/fc-devicemgmt/tlsoption-devicemgmt-agent-mtls.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "traefik.io/v1alpha1",
|
||||||
|
"kind": "TLSOption",
|
||||||
|
"metadata": {
|
||||||
|
"name": "devicemgmt-agent-mtls",
|
||||||
|
"namespace": "fc-devicemgmt"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"clientAuth": {
|
||||||
|
"clientAuthType": "RequireAndVerifyClientCert",
|
||||||
|
"secretNames": [
|
||||||
|
"devicemgmt-agent-client-ca"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
"value": "*"
|
"value": "*"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-distribution:gx10-v1",
|
"image": "localhost/fc-distribution:v20260619-distribution-592ad75",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"livenessProbe": {
|
"livenessProbe": {
|
||||||
"failureThreshold": 3,
|
"failureThreshold": 3,
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-dms-web:gx10-v1",
|
"image": "localhost/fc-dms-web:v20260619-dms-b203a71",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"livenessProbe": {
|
"livenessProbe": {
|
||||||
"failureThreshold": 3,
|
"failureThreshold": 3,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ spec:
|
|||||||
fsGroupChangePolicy: OnRootMismatch
|
fsGroupChangePolicy: OnRootMismatch
|
||||||
containers:
|
containers:
|
||||||
- name: web
|
- name: web
|
||||||
image: localhost/fc-gateway:v20260617-hm1-gateway-e0627e3
|
image: localhost/fc-gateway:v20260619-sec3-429e6cf
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 8080
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-library-web:gx10-v1",
|
"image": "localhost/fc-library-web:v20260619-library-0e027cc",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"livenessProbe": {
|
"livenessProbe": {
|
||||||
"failureThreshold": 3,
|
"failureThreshold": 3,
|
||||||
|
|||||||
@@ -176,7 +176,7 @@
|
|||||||
"value": "00:05:00"
|
"value": "00:05:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-llm-bridge:gx10-v2",
|
"image": "localhost/fc-llm-bridge:v20260619-llmbridge-6ba5986",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"livenessProbe": {
|
"livenessProbe": {
|
||||||
"failureThreshold": 3,
|
"failureThreshold": 3,
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
"value": "mysql"
|
"value": "mysql"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-mysql-web:v20260617-sec4-storage-6fc3739",
|
"image": "localhost/fc-mysql-web:v20260618-hm4-tenant-84dc65c",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"livenessProbe": {
|
"livenessProbe": {
|
||||||
"failureThreshold": 3,
|
"failureThreshold": 3,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"data": {
|
"data": {
|
||||||
"appsettings.Production.json": "{\"PhpManager\":{\"Namespace\":\"fc-php\",\"Slowlog\":{\"Path\":\"/var/log/apache2/php-fpm-slow.log\",\"Sidecar\":{\"Enabled\":true,\"Image\":\"\"}},\"PoolConfig\":{\"StartServers\":null,\"MinSpareServers\":null,\"MaxSpareServers\":null,\"ProcessIdleTimeoutSeconds\":10,\"RequestTerminateTimeoutSeconds\":30},\"Certificates\":{\"TlsInspector\":{\"LogGracefulDegradeWarnings\":false}},\"Backups\":{\"StoragePath\":\"/data/backups\"},\"Ingress\":{\"DefaultMiddlewares\":[{\"Name\":\"php-tenant-rate-limit\",\"Namespace\":\"fc-php\"},{\"Name\":\"php-tenant-secure-headers\",\"Namespace\":\"fc-php\"}],\"TlsOption\":{\"Name\":\"php-tenant-tls13\",\"Namespace\":\"fc-php\"}}},\"ApplicationArchives\":{\"WordPressCoreUrl\":\"http://php-web.fc-php.svc.cluster.local.:5400/api/v1/application-archives/wordpress/latest.tar.gz\",\"WordPressProxySourceUrl\":\"https://wordpress.org/latest.tar.gz\",\"WordPressLocalArchivePath\":\"/data/application-archives/latest.tar.gz\",\"MyBbCoreUrl\":\"http://php-web.fc-php.svc.cluster.local.:5400/api/v1/application-archives/mybb/latest.zip\",\"MyBbProxySourceUrl\":\"https://mybb.com/download/\",\"MyBbLocalArchivePath\":\"/data/application-archives/mybb-latest.zip\",\"MediaWikiCoreUrl\":\"http://php-web.fc-php.svc.cluster.local.:5400/api/v1/application-archives/mediawiki/latest.tar.gz\",\"MediaWikiProxySourceUrl\":\"https://releases.wikimedia.org/mediawiki/1.45/mediawiki-1.45.3.tar.gz\",\"MediaWikiLocalArchivePath\":\"/data/application-archives/mediawiki-latest.tar.gz\",\"DrupalCoreUrl\":\"http://php-web.fc-php.svc.cluster.local.:5400/api/v1/application-archives/drupal/latest.tar.gz\",\"DrupalProxySourceUrl\":\"https://ftp.drupal.org/files/projects/drupal-11.3.8.tar.gz\",\"DrupalLocalArchivePath\":\"/data/application-archives/drupal-latest.tar.gz\",\"BypassUpstreamTls\":true},\"ContainerBackend\":{\"Default\":\"Kubernetes\"},\"FlowerCore\":{\"Auth\":{\"Provider\":\"Oidc\",\"Enabled\":false,\"Oidc\":{\"Enabled\":true,\"Authority\":\"https://id.iamworkin.lan/application/o/php/\",\"Audience\":\"php\",\"ClientId\":\"php\",\"ClientSecret\":\"\"},\"Impersonation\":{\"Enabled\":false,\"DebugMode\":false}},\"Tenant\":{\"StrictMode\":false,\"JwtClaimsEnabled\":false,\"TenantClaimType\":\"fc:tenant\",\"ActorIdClaimType\":\"flowercore_actor_id\"},\"Account\":{\"AppId\":\"php\",\"DefaultTenantId\":\"default\",\"Impersonation\":{\"Enabled\":false,\"StrictMode\":false,\"TechSupportRoles\":[\"tech-support\"],\"Targets\":[]}},\"Hosting\":{\"AutoDns\":{\"Enabled\":true,\"DnsManagerBaseUrl\":\"https://dns.iamworkin.lan/\",\"ZoneName\":\"iamworkin.lan\",\"RecordType\":\"A\",\"TargetAddress\":\"10.0.56.200\",\"Ttl\":300,\"BypassTls\":true}},\"Database\":{\"Provider\":\"Sqlite\",\"ConnectionStrings\":{\"Sqlite\":\"Data Source=/data/php-manager.db\"}}}}"
|
"appsettings.Production.json": "{\"PhpManager\":{\"Namespace\":\"fc-php\",\"Slowlog\":{\"Path\":\"/var/log/apache2/php-fpm-slow.log\",\"Sidecar\":{\"Enabled\":true,\"Image\":\"\"}},\"PoolConfig\":{\"StartServers\":null,\"MinSpareServers\":null,\"MaxSpareServers\":null,\"ProcessIdleTimeoutSeconds\":10,\"RequestTerminateTimeoutSeconds\":30},\"Certificates\":{\"TlsInspector\":{\"LogGracefulDegradeWarnings\":false}},\"Backups\":{\"StoragePath\":\"/data/backups\"},\"Ingress\":{\"DefaultMiddlewares\":[{\"Name\":\"php-tenant-rate-limit\",\"Namespace\":\"fc-php\"},{\"Name\":\"php-tenant-secure-headers\",\"Namespace\":\"fc-php\"}],\"TlsOption\":{\"Name\":\"php-tenant-tls13\",\"Namespace\":\"fc-php\"},\"Waf\":{\"Enabled\":true,\"Image\":\"owasp/modsecurity-crs:4.25-nginx-alpine-lts@sha256:88b59911549723e71beabf3b4aa47bbd31b00e79401f442e65ddfc430ae46343\",\"AllowedMethods\":\"GET HEAD POST OPTIONS DELETE\"}}},\"ApplicationArchives\":{\"WordPressCoreUrl\":\"http://php-web.fc-php.svc.cluster.local.:5400/api/v1/application-archives/wordpress/latest.tar.gz\",\"WordPressProxySourceUrl\":\"https://wordpress.org/latest.tar.gz\",\"WordPressLocalArchivePath\":\"/data/application-archives/latest.tar.gz\",\"MyBbCoreUrl\":\"http://php-web.fc-php.svc.cluster.local.:5400/api/v1/application-archives/mybb/latest.zip\",\"MyBbProxySourceUrl\":\"https://mybb.com/download/\",\"MyBbLocalArchivePath\":\"/data/application-archives/mybb-latest.zip\",\"MediaWikiCoreUrl\":\"http://php-web.fc-php.svc.cluster.local.:5400/api/v1/application-archives/mediawiki/latest.tar.gz\",\"MediaWikiProxySourceUrl\":\"https://releases.wikimedia.org/mediawiki/1.45/mediawiki-1.45.3.tar.gz\",\"MediaWikiLocalArchivePath\":\"/data/application-archives/mediawiki-latest.tar.gz\",\"DrupalCoreUrl\":\"http://php-web.fc-php.svc.cluster.local.:5400/api/v1/application-archives/drupal/latest.tar.gz\",\"DrupalProxySourceUrl\":\"https://ftp.drupal.org/files/projects/drupal-11.3.8.tar.gz\",\"DrupalLocalArchivePath\":\"/data/application-archives/drupal-latest.tar.gz\",\"BypassUpstreamTls\":true},\"ContainerBackend\":{\"Default\":\"Kubernetes\"},\"FlowerCore\":{\"Auth\":{\"Provider\":\"Oidc\",\"Enabled\":false,\"Oidc\":{\"Enabled\":true,\"Authority\":\"https://id.iamworkin.lan/application/o/php/\",\"Audience\":\"php\",\"ClientId\":\"php\",\"ClientSecret\":\"\"},\"Impersonation\":{\"Enabled\":false,\"DebugMode\":false}},\"Tenant\":{\"StrictMode\":false,\"JwtClaimsEnabled\":false,\"TenantClaimType\":\"fc:tenant\",\"ActorIdClaimType\":\"flowercore_actor_id\"},\"Account\":{\"AppId\":\"php\",\"DefaultTenantId\":\"default\",\"Impersonation\":{\"Enabled\":false,\"StrictMode\":false,\"TechSupportRoles\":[\"tech-support\"],\"Targets\":[]}},\"Hosting\":{\"AutoDns\":{\"Enabled\":true,\"DnsManagerBaseUrl\":\"https://dns.iamworkin.lan/\",\"ZoneName\":\"iamworkin.lan\",\"RecordType\":\"A\",\"TargetAddress\":\"10.0.57.202\",\"Ttl\":300,\"BypassTls\":true}},\"Database\":{\"Provider\":\"Sqlite\",\"ConnectionStrings\":{\"Sqlite\":\"Data Source=/data/php-manager.db\"}}}}"
|
||||||
},
|
},
|
||||||
"kind": "ConfigMap",
|
"kind": "ConfigMap",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
|||||||
@@ -67,6 +67,10 @@
|
|||||||
"name": "MODSEC_AUDIT_LOG_TYPE",
|
"name": "MODSEC_AUDIT_LOG_TYPE",
|
||||||
"value": "Serial"
|
"value": "Serial"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "ALLOWED_METHODS",
|
||||||
|
"value": "GET HEAD POST OPTIONS DELETE"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "LOGLEVEL",
|
"name": "LOGLEVEL",
|
||||||
"value": "warn"
|
"value": "warn"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"template": {
|
"template": {
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"annotations": {
|
"annotations": {
|
||||||
"kubectl.kubernetes.io/restartedAt": "2026-06-13T01:59:27-05:00",
|
"kubectl.kubernetes.io/restartedAt": "2026-06-19T00:00:00-05:00",
|
||||||
"prometheus.io/path": "/metrics/prometheus",
|
"prometheus.io/path": "/metrics/prometheus",
|
||||||
"prometheus.io/port": "5400",
|
"prometheus.io/port": "5400",
|
||||||
"prometheus.io/scrape": "true"
|
"prometheus.io/scrape": "true"
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
"value": "php"
|
"value": "php"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-php-web:v20260617-whc4-edge-638b3b3",
|
"image": "localhost/fc-php-web:v20260619-whc4-generated-waf-147f02a",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"livenessProbe": {
|
"livenessProbe": {
|
||||||
"failureThreshold": 3,
|
"failureThreshold": 3,
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-presentations:gx10-v1",
|
"image": "localhost/fc-presentations:v20260619-presentations-a67ef22",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"livenessProbe": {
|
"livenessProbe": {
|
||||||
"failureThreshold": 3,
|
"failureThreshold": 3,
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
"-lc",
|
"-lc",
|
||||||
"set -eu\nmkdir -p /mnt/pvc/data /mnt/pvc/uploads\n\nfor file in presentations.db presentations.db-shm presentations.db-wal; do\n if [ -f \"/mnt/pvc/${file}\" ] && [ ! -f \"/mnt/pvc/data/${file}\" ]; then\n mv \"/mnt/pvc/${file}\" \"/mnt/pvc/data/${file}\"\n fi\ndone\n\nif [ -d /mnt/pvc/dp-keys ] && [ ! -d /mnt/pvc/data/dp-keys ]; then\n mv /mnt/pvc/dp-keys /mnt/pvc/data/dp-keys\nfi\n\nfor directory in imports slides html-bundles; do\n if [ -d \"/mnt/pvc/${directory}\" ] && [ ! -d \"/mnt/pvc/uploads/${directory}\" ]; then\n mv \"/mnt/pvc/${directory}\" \"/mnt/pvc/uploads/${directory}\"\n fi\ndone\n\nmkdir -p \\\n /mnt/pvc/data/dp-keys \\\n /mnt/pvc/uploads/imports \\\n /mnt/pvc/uploads/slides \\\n /mnt/pvc/uploads/html-bundles\n"
|
"set -eu\nmkdir -p /mnt/pvc/data /mnt/pvc/uploads\n\nfor file in presentations.db presentations.db-shm presentations.db-wal; do\n if [ -f \"/mnt/pvc/${file}\" ] && [ ! -f \"/mnt/pvc/data/${file}\" ]; then\n mv \"/mnt/pvc/${file}\" \"/mnt/pvc/data/${file}\"\n fi\ndone\n\nif [ -d /mnt/pvc/dp-keys ] && [ ! -d /mnt/pvc/data/dp-keys ]; then\n mv /mnt/pvc/dp-keys /mnt/pvc/data/dp-keys\nfi\n\nfor directory in imports slides html-bundles; do\n if [ -d \"/mnt/pvc/${directory}\" ] && [ ! -d \"/mnt/pvc/uploads/${directory}\" ]; then\n mv \"/mnt/pvc/${directory}\" \"/mnt/pvc/uploads/${directory}\"\n fi\ndone\n\nmkdir -p \\\n /mnt/pvc/data/dp-keys \\\n /mnt/pvc/uploads/imports \\\n /mnt/pvc/uploads/slides \\\n /mnt/pvc/uploads/html-bundles\n"
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-presentations:gx10-v1",
|
"image": "localhost/fc-presentations:v20260619-presentations-a67ef22",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"name": "storage-init",
|
"name": "storage-init",
|
||||||
"resources": {},
|
"resources": {},
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-retail-web:gx10-v1",
|
"image": "localhost/fc-retail-web:v20260619-retail-faae9db",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"livenessProbe": {
|
"livenessProbe": {
|
||||||
"failureThreshold": 3,
|
"failureThreshold": 3,
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-scoreboard-web:gx10-v1",
|
"image": "localhost/fc-scoreboard-web:v20260619-scoreboard-981d4b5",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"livenessProbe": {
|
"livenessProbe": {
|
||||||
"failureThreshold": 3,
|
"failureThreshold": 3,
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
"-c",
|
"-c",
|
||||||
"chown -R 1654:1654 /data && chmod -R u+rwX,g+rwX /data"
|
"chown -R 1654:1654 /data && chmod -R u+rwX,g+rwX /data"
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-scoreboard-web:gx10-v1",
|
"image": "localhost/fc-scoreboard-web:v20260619-scoreboard-981d4b5",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"name": "scoreboard-data-permissions",
|
"name": "scoreboard-data-permissions",
|
||||||
"resources": {},
|
"resources": {},
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
"value": "http://dms-web.fc-dms.svc.cluster.local.:8081"
|
"value": "http://dms-web.fc-dms.svc.cluster.local.:8081"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-signage-replay-web:gx10-v1",
|
"image": "localhost/fc-signage-replay-web:v20260619-signage-replay-4da0983",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"livenessProbe": {
|
"livenessProbe": {
|
||||||
"failureThreshold": 3,
|
"failureThreshold": 3,
|
||||||
|
|||||||
@@ -106,7 +106,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-kiosk-web:gx10-v1",
|
"image": "localhost/fc-kiosk-web:v20260619-kiadmin-7cc83fd",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"livenessProbe": {
|
"livenessProbe": {
|
||||||
"failureThreshold": 6,
|
"failureThreshold": 6,
|
||||||
@@ -195,7 +195,7 @@
|
|||||||
"-c",
|
"-c",
|
||||||
"mkdir -p /profiles/data && chown -R 1654:1654 /profiles/data && chmod -R u+rwX,g+rwX /profiles/data"
|
"mkdir -p /profiles/data && chown -R 1654:1654 /profiles/data && chmod -R u+rwX,g+rwX /profiles/data"
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-kiosk-web:gx10-v1",
|
"image": "localhost/fc-kiosk-web:v20260619-kiadmin-7cc83fd",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"name": "fix-profile-perms",
|
"name": "fix-profile-perms",
|
||||||
"resources": {},
|
"resources": {},
|
||||||
|
|||||||
@@ -66,11 +66,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "PhpManager__BaseUrl",
|
"name": "PhpManager__BaseUrl",
|
||||||
"value": "https://php.iamworkin.lan/"
|
"value": "http://php-web.fc-php.svc.cluster.local:5400/"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "PhpManager__BypassTls",
|
"name": "PhpManager__BypassTls",
|
||||||
"value": "true"
|
"value": "false"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-php-operator:v20260617-sec5-0bfbf42",
|
"image": "localhost/fc-php-operator:v20260617-sec5-0bfbf42",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"data": {
|
"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",
|
"kind": "ConfigMap",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
|||||||
180
apps-gx10/fc-tenant-andrew/deployment-andrew-web-waf.json
Normal file
180
apps-gx10/fc-tenant-andrew/deployment-andrew-web-waf.json
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": {
|
||||||
|
"labels": {
|
||||||
|
"app.kubernetes.io/managed-by": "flowercore",
|
||||||
|
"app.kubernetes.io/name": "andrew-web-waf"
|
||||||
|
},
|
||||||
|
"name": "andrew-web-waf",
|
||||||
|
"namespace": "fc-tenant-andrew"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"progressDeadlineSeconds": 600,
|
||||||
|
"replicas": 1,
|
||||||
|
"revisionHistoryLimit": 10,
|
||||||
|
"selector": {
|
||||||
|
"matchLabels": {
|
||||||
|
"app.kubernetes.io/name": "andrew-web-waf"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"strategy": {
|
||||||
|
"type": "Recreate"
|
||||||
|
},
|
||||||
|
"template": {
|
||||||
|
"metadata": {
|
||||||
|
"labels": {
|
||||||
|
"app.kubernetes.io/name": "andrew-web-waf"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"containers": [
|
||||||
|
{
|
||||||
|
"env": [
|
||||||
|
{
|
||||||
|
"name": "BACKEND",
|
||||||
|
"value": "http://andrew-web.fc-tenant-andrew.svc.cluster.local:80"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SERVER_NAME",
|
||||||
|
"value": "bluejay.dev www.bluejay.dev"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PORT",
|
||||||
|
"value": "8080"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PROXY_PRESERVE_HOST",
|
||||||
|
"value": "on"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PROXY_TIMEOUT",
|
||||||
|
"value": "60s"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MODSEC_RULE_ENGINE",
|
||||||
|
"value": "On"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MODSEC_AUDIT_ENGINE",
|
||||||
|
"value": "RelevantOnly"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MODSEC_AUDIT_LOG",
|
||||||
|
"value": "/dev/stdout"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MODSEC_AUDIT_LOG_TYPE",
|
||||||
|
"value": "Serial"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LOGLEVEL",
|
||||||
|
"value": "warn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ERRORLOG",
|
||||||
|
"value": "/dev/stderr"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ACCESSLOG",
|
||||||
|
"value": "/dev/stdout"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BLOCKING_PARANOIA",
|
||||||
|
"value": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DETECTION_PARANOIA",
|
||||||
|
"value": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ANOMALY_INBOUND",
|
||||||
|
"value": "5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ANOMALY_OUTBOUND",
|
||||||
|
"value": "4"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"image": "owasp/modsecurity-crs:4.25-nginx-alpine-lts@sha256:88b59911549723e71beabf3b4aa47bbd31b00e79401f442e65ddfc430ae46343",
|
||||||
|
"imagePullPolicy": "IfNotPresent",
|
||||||
|
"livenessProbe": {
|
||||||
|
"failureThreshold": 3,
|
||||||
|
"httpGet": {
|
||||||
|
"httpHeaders": [
|
||||||
|
{
|
||||||
|
"name": "Host",
|
||||||
|
"value": "bluejay.dev"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"path": "/healthz",
|
||||||
|
"port": 8080,
|
||||||
|
"scheme": "HTTP"
|
||||||
|
},
|
||||||
|
"initialDelaySeconds": 20,
|
||||||
|
"periodSeconds": 30,
|
||||||
|
"successThreshold": 1,
|
||||||
|
"timeoutSeconds": 2
|
||||||
|
},
|
||||||
|
"name": "andrew-web-waf",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"containerPort": 8080,
|
||||||
|
"name": "http",
|
||||||
|
"protocol": "TCP"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"readinessProbe": {
|
||||||
|
"failureThreshold": 3,
|
||||||
|
"httpGet": {
|
||||||
|
"httpHeaders": [
|
||||||
|
{
|
||||||
|
"name": "Host",
|
||||||
|
"value": "bluejay.dev"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"path": "/healthz",
|
||||||
|
"port": 8080,
|
||||||
|
"scheme": "HTTP"
|
||||||
|
},
|
||||||
|
"initialDelaySeconds": 10,
|
||||||
|
"periodSeconds": 10,
|
||||||
|
"successThreshold": 1,
|
||||||
|
"timeoutSeconds": 2
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"limits": {
|
||||||
|
"cpu": "500m",
|
||||||
|
"memory": "512Mi"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"cpu": "100m",
|
||||||
|
"memory": "128Mi"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securityContext": {
|
||||||
|
"allowPrivilegeEscalation": false,
|
||||||
|
"capabilities": {
|
||||||
|
"drop": [
|
||||||
|
"ALL"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"terminationMessagePath": "/dev/termination-log",
|
||||||
|
"terminationMessagePolicy": "File"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"enableServiceLinks": false,
|
||||||
|
"restartPolicy": "Always",
|
||||||
|
"schedulerName": "default-scheduler",
|
||||||
|
"securityContext": {
|
||||||
|
"fsGroup": 101,
|
||||||
|
"runAsGroup": 101,
|
||||||
|
"runAsNonRoot": true,
|
||||||
|
"runAsUser": 101
|
||||||
|
},
|
||||||
|
"terminationGracePeriodSeconds": 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,9 @@
|
|||||||
},
|
},
|
||||||
"template": {
|
"template": {
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
"annotations": {
|
||||||
|
"flowercore.io/config-revision": "whc4-lamp-allowlist-20260618"
|
||||||
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"app": "andrew-web"
|
"app": "andrew-web"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,16 +13,55 @@
|
|||||||
{
|
{
|
||||||
"kind": "Rule",
|
"kind": "Rule",
|
||||||
"match": "Host(`bluejay.dev`) || Host(`www.bluejay.dev`)",
|
"match": "Host(`bluejay.dev`) || Host(`www.bluejay.dev`)",
|
||||||
|
"middlewares": [
|
||||||
|
{
|
||||||
|
"name": "andrew-tenant-rate-limit",
|
||||||
|
"namespace": "fc-tenant-andrew"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "andrew-tenant-secure-headers",
|
||||||
|
"namespace": "fc-tenant-andrew"
|
||||||
|
}
|
||||||
|
],
|
||||||
"priority": 100,
|
"priority": 100,
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
"name": "andrew-web",
|
"name": "andrew-web-waf",
|
||||||
"port": 80
|
"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": {
|
"tls": {
|
||||||
|
"options": {
|
||||||
|
"name": "andrew-tenant-tls13",
|
||||||
|
"namespace": "fc-tenant-andrew"
|
||||||
|
},
|
||||||
"secretName": "cf-origin-bluejay-dev"
|
"secretName": "cf-origin-bluejay-dev"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "traefik.io/v1alpha1",
|
||||||
|
"kind": "Middleware",
|
||||||
|
"metadata": {
|
||||||
|
"name": "andrew-tenant-rate-limit",
|
||||||
|
"namespace": "fc-tenant-andrew"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"rateLimit": {
|
||||||
|
"average": 120,
|
||||||
|
"burst": 240,
|
||||||
|
"period": "1m"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "traefik.io/v1alpha1",
|
||||||
|
"kind": "Middleware",
|
||||||
|
"metadata": {
|
||||||
|
"name": "andrew-tenant-secure-headers",
|
||||||
|
"namespace": "fc-tenant-andrew"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"headers": {
|
||||||
|
"contentTypeNosniff": true,
|
||||||
|
"browserXssFilter": true,
|
||||||
|
"referrerPolicy": "strict-origin-when-cross-origin",
|
||||||
|
"stsSeconds": 31536000,
|
||||||
|
"stsIncludeSubdomains": true,
|
||||||
|
"stsPreload": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
apps-gx10/fc-tenant-andrew/service-andrew-web-waf.json
Normal file
24
apps-gx10/fc-tenant-andrew/service-andrew-web-waf.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Service",
|
||||||
|
"metadata": {
|
||||||
|
"name": "andrew-web-waf",
|
||||||
|
"namespace": "fc-tenant-andrew"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"internalTrafficPolicy": "Cluster",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"name": "http",
|
||||||
|
"port": 8080,
|
||||||
|
"protocol": "TCP",
|
||||||
|
"targetPort": 8080
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selector": {
|
||||||
|
"app.kubernetes.io/name": "andrew-web-waf"
|
||||||
|
},
|
||||||
|
"sessionAffinity": "None",
|
||||||
|
"type": "ClusterIP"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "traefik.io/v1alpha1",
|
||||||
|
"kind": "TLSOption",
|
||||||
|
"metadata": {
|
||||||
|
"name": "andrew-tenant-tls13",
|
||||||
|
"namespace": "fc-tenant-andrew"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"minVersion": "VersionTLS13"
|
||||||
|
}
|
||||||
|
}
|
||||||
11
apps-gx10/fc-tenant-default/namespace-fc-tenant-default.json
Normal file
11
apps-gx10/fc-tenant-default/namespace-fc-tenant-default.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Namespace",
|
||||||
|
"metadata": {
|
||||||
|
"labels": {
|
||||||
|
"app.kubernetes.io/managed-by": "flowercore",
|
||||||
|
"flowercore.io/tenant": "default"
|
||||||
|
},
|
||||||
|
"name": "fc-tenant-default"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,6 +57,10 @@
|
|||||||
"name": "FlowerCore__Updater__PublicShares__RequirePublicVisibilityOnPublicHosts",
|
"name": "FlowerCore__Updater__PublicShares__RequirePublicVisibilityOnPublicHosts",
|
||||||
"value": "true"
|
"value": "true"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__Updater__PublicShares__RequireShareLinkOnPublicHosts",
|
||||||
|
"value": "true"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "FlowerCore__Updater__PublicShares__Links__0__Code",
|
"name": "FlowerCore__Updater__PublicShares__Links__0__Code",
|
||||||
"value": "8f3c2a9e7d41"
|
"value": "8f3c2a9e7d41"
|
||||||
@@ -85,6 +89,38 @@
|
|||||||
"name": "FlowerCore__Updater__PublicShares__Links__0__Description",
|
"name": "FlowerCore__Updater__PublicShares__Links__0__Description",
|
||||||
"value": "Private release link for Mike's Faith AI bundle."
|
"value": "Private release link for Mike's Faith AI bundle."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__Updater__PublicShares__Links__1__RoutePrefix",
|
||||||
|
"value": "kiosk-bundle"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__Updater__PublicShares__Links__1__Code",
|
||||||
|
"value": "b22f04a5ff39"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__Updater__PublicShares__Links__1__AppId",
|
||||||
|
"value": "flowercore.kiosk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__Updater__PublicShares__Links__1__Channel",
|
||||||
|
"value": "stable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__Updater__PublicShares__Links__1__RuntimeId",
|
||||||
|
"value": "win-x64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__Updater__PublicShares__Links__1__DisplayName",
|
||||||
|
"value": "FlowerCore Kiosk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__Updater__PublicShares__Links__1__Headline",
|
||||||
|
"value": "FlowerCore Kiosk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__Updater__PublicShares__Links__1__Description",
|
||||||
|
"value": "Private release link for the FlowerCore Kiosk Windows installer."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "FlowerCore__Audit__Sinks__Loki__Enabled",
|
"name": "FlowerCore__Audit__Sinks__Loki__Enabled",
|
||||||
"value": "false"
|
"value": "false"
|
||||||
@@ -195,7 +231,7 @@
|
|||||||
"value": "26843545600"
|
"value": "26843545600"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-updater-web:v20260617-sec5-913c6a9",
|
"image": "localhost/fc-updater-web:v20260618-feed-signed-9cc9942",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"securityContext": {
|
"securityContext": {
|
||||||
"allowPrivilegeEscalation": false,
|
"allowPrivilegeEscalation": false,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
"kind": "Rule",
|
"kind": "Rule",
|
||||||
"match": "(Host(`update.flowercore.io`) || Host(`updates.flowercore.io`)) && (Method(`GET`) || Method(`HEAD`) || Method(`POST`) || Method(`OPTIONS`))",
|
"match": "(Host(`update.flowercore.io`) || Host(`updates.flowercore.io`)) && (Method(`GET`) || Method(`HEAD`))",
|
||||||
"priority": 100,
|
"priority": 100,
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -101,7 +101,7 @@
|
|||||||
"value": "false"
|
"value": "false"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-worldbuilder:gx10-v1",
|
"image": "localhost/fc-worldbuilder:v20260619-worldbuilder-edd6efc",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"livenessProbe": {
|
"livenessProbe": {
|
||||||
"failureThreshold": 3,
|
"failureThreshold": 3,
|
||||||
|
|||||||
@@ -147,7 +147,7 @@
|
|||||||
"value": "/data/vector-stores/corpus-cache"
|
"value": "/data/vector-stores/corpus-cache"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-knowledge-web:gx10-v1",
|
"image": "localhost/fc-knowledge-web:v20260619-sec3-6370c95",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"livenessProbe": {
|
"livenessProbe": {
|
||||||
"failureThreshold": 3,
|
"failureThreshold": 3,
|
||||||
|
|||||||
@@ -10,8 +10,9 @@
|
|||||||
# Phase 1 production uses a Longhorn RWO PVC at /data/devicemgmt.db. The
|
# Phase 1 production uses a Longhorn RWO PVC at /data/devicemgmt.db. The
|
||||||
# 1Password runtime item stays mounted through env for future MySQL/API-key
|
# 1Password runtime item stays mounted through env for future MySQL/API-key
|
||||||
# cutover, but MySQL is not required for this first product-host rollout.
|
# cutover, but MySQL is not required for this first product-host rollout.
|
||||||
# Image v20260613-g2-66a43c1 is built from FlowerCore.DeviceManagement master
|
# Image v20260618-prune-18c7449-livebase is derived from the 2026-06-17 AN-13
|
||||||
# 66a43c1, carrying edge enrollment network completion and SQLite-safe trust-bundle smoke coverage.
|
# live base with the Mac fleet SQLite snapshot-prune hotfix from
|
||||||
|
# FlowerCore.DeviceManagement PR #49.
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: PersistentVolumeClaim
|
kind: PersistentVolumeClaim
|
||||||
@@ -83,7 +84,7 @@ spec:
|
|||||||
fsGroupChangePolicy: OnRootMismatch
|
fsGroupChangePolicy: OnRootMismatch
|
||||||
containers:
|
containers:
|
||||||
- name: web
|
- name: web
|
||||||
image: localhost/fc-devicemgmt-web:v20260614-regroup-c5b8f82
|
image: localhost/fc-devicemgmt-web:v20260618-prune-18c7449-livebase
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
|
|||||||
@@ -43,5 +43,6 @@ shared origin cert must exist in every namespace that serves a
|
|||||||
```powershell
|
```powershell
|
||||||
kubectl.exe --kubeconfig C:\Users\AndrewStoltz\.kube\rke2.yaml -n argocd get application infra-fc-updater
|
kubectl.exe --kubeconfig C:\Users\AndrewStoltz\.kube\rke2.yaml -n argocd get application infra-fc-updater
|
||||||
kubectl.exe --kubeconfig C:\Users\AndrewStoltz\.kube\rke2.yaml -n fc-updater get deploy,svc,ingressroute,certificate,pvc
|
kubectl.exe --kubeconfig C:\Users\AndrewStoltz\.kube\rke2.yaml -n fc-updater get deploy,svc,ingressroute,certificate,pvc
|
||||||
curl.exe -sk https://update.flowercore.io/api/v1/manifests/_schema
|
curl.exe -sk https://update.flowercore.io/
|
||||||
|
curl.exe -sk -o NUL -w "%{http_code}`n" https://update.flowercore.io/login
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ spec:
|
|||||||
nodeName: rke2-server
|
nodeName: rke2-server
|
||||||
containers:
|
containers:
|
||||||
- name: web
|
- name: web
|
||||||
image: localhost/fc-updater-web:v20260614-regroup-bdf4a4a
|
image: localhost/fc-updater-web:v20260618-feed-signed-9cc9942
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 8080
|
||||||
@@ -92,6 +92,22 @@ spec:
|
|||||||
value: Faith AI Mike Edition
|
value: Faith AI Mike Edition
|
||||||
- name: FlowerCore__Updater__PublicShares__Links__0__Description
|
- name: FlowerCore__Updater__PublicShares__Links__0__Description
|
||||||
value: Private release link for Mike's Faith AI bundle.
|
value: Private release link for Mike's Faith AI bundle.
|
||||||
|
- name: FlowerCore__Updater__PublicShares__Links__1__RoutePrefix
|
||||||
|
value: kiosk-bundle
|
||||||
|
- name: FlowerCore__Updater__PublicShares__Links__1__Code
|
||||||
|
value: b22f04a5ff39
|
||||||
|
- name: FlowerCore__Updater__PublicShares__Links__1__AppId
|
||||||
|
value: flowercore.kiosk
|
||||||
|
- name: FlowerCore__Updater__PublicShares__Links__1__Channel
|
||||||
|
value: stable
|
||||||
|
- name: FlowerCore__Updater__PublicShares__Links__1__RuntimeId
|
||||||
|
value: win-x64
|
||||||
|
- name: FlowerCore__Updater__PublicShares__Links__1__DisplayName
|
||||||
|
value: FlowerCore Kiosk
|
||||||
|
- name: FlowerCore__Updater__PublicShares__Links__1__Headline
|
||||||
|
value: FlowerCore Kiosk
|
||||||
|
- name: FlowerCore__Updater__PublicShares__Links__1__Description
|
||||||
|
value: Private release link for the FlowerCore Kiosk Windows installer.
|
||||||
- name: FlowerCore__Audit__Sinks__Loki__Enabled
|
- name: FlowerCore__Audit__Sinks__Loki__Enabled
|
||||||
value: "false"
|
value: "false"
|
||||||
- name: FlowerCore__Updater__Auth__Bootstrap__Enabled
|
- name: FlowerCore__Updater__Auth__Bootstrap__Enabled
|
||||||
@@ -266,7 +282,7 @@ spec:
|
|||||||
entryPoints:
|
entryPoints:
|
||||||
- websecure
|
- websecure
|
||||||
routes:
|
routes:
|
||||||
- match: (Host(`update.flowercore.io`) || Host(`updates.flowercore.io`)) && (Method(`GET`) || Method(`HEAD`) || Method(`POST`) || Method(`OPTIONS`))
|
- match: (Host(`update.flowercore.io`) || Host(`updates.flowercore.io`)) && (Method(`GET`) || Method(`HEAD`))
|
||||||
kind: Rule
|
kind: Rule
|
||||||
services:
|
services:
|
||||||
- name: updatecenter-web
|
- name: updatecenter-web
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ auto-deploy them there. Once ArgoCD is stood up on the GX10, a GX10-only
|
|||||||
ApplicationSet (`apps-gx10/*`) will own these.
|
ApplicationSet (`apps-gx10/*`) will own these.
|
||||||
|
|
||||||
- `step-ca-acme.yaml` — cert-manager ClusterIssuer (ACME → noc1 step-ca, in-spec caBundle). APPLIED + Ready.
|
- `step-ca-acme.yaml` — cert-manager ClusterIssuer (ACME → noc1 step-ca, in-spec caBundle). APPLIED + Ready.
|
||||||
- `traefik-helmchart.yaml` — Traefik v3.6.10 (chart 39.0.5) via the RKE2 HelmChart CRD, LoadBalancer VIP 10.0.57.202 (prod-pool; temp parallel-run VIP — canonical .200 reclaimed at cutover). APPLIED.
|
- `traefik-helmchart.yaml` — Traefik v3.6.10 (chart 39.0.5) via the RKE2 HelmChart CRD, LoadBalancer VIP 10.0.57.202 (prod-pool; temp parallel-run VIP — canonical .200 reclaimed at cutover), with `externalTrafficPolicy: Local` so tenant IP allowlists see client source IP instead of the GX10 node hop. APPLIED.
|
||||||
|
- `gitea-ssh-service.yaml` — Gitea SSH LoadBalancer service on `10.0.57.206:22` with `externalTrafficPolicy: Local`; HTTPS Gitea remains behind the Traefik VIP at `10.0.57.202`. APPLIED.
|
||||||
|
|
||||||
cert-manager v1.17.2 was installed separately (upstream static manifest). See
|
cert-manager v1.17.2 was installed separately (upstream static manifest). See
|
||||||
`docs/ai-agents/gx10-migration-continuation-2026-06-14.md` + memory
|
`docs/ai-agents/gx10-migration-continuation-2026-06-14.md` + memory
|
||||||
|
|||||||
17
gx10/platform/gitea-ssh-service.yaml
Normal file
17
gx10/platform/gitea-ssh-service.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: gitea-ssh
|
||||||
|
namespace: gitea
|
||||||
|
annotations:
|
||||||
|
metallb.io/loadBalancerIPs: 10.0.57.206
|
||||||
|
spec:
|
||||||
|
type: LoadBalancer
|
||||||
|
externalTrafficPolicy: Local
|
||||||
|
selector:
|
||||||
|
app: gitea
|
||||||
|
ports:
|
||||||
|
- name: ssh
|
||||||
|
port: 22
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 2222
|
||||||
@@ -10,72 +10,27 @@ spec:
|
|||||||
targetNamespace: traefik-system
|
targetNamespace: traefik-system
|
||||||
createNamespace: true
|
createNamespace: true
|
||||||
valuesContent: |
|
valuesContent: |
|
||||||
deployment:
|
service:
|
||||||
replicas: 1
|
type: LoadBalancer
|
||||||
additionalArguments:
|
spec:
|
||||||
- "--api.dashboard=true"
|
externalTrafficPolicy: Local
|
||||||
- "--log.level=INFO"
|
annotations:
|
||||||
- "--providers.kubernetescrd"
|
metallb.io/loadBalancerIPs: 10.0.57.202
|
||||||
- "--providers.kubernetesingress"
|
ingressClass:
|
||||||
- "--providers.kubernetescrd.allowEmptyServices=true"
|
enabled: true
|
||||||
- "--providers.kubernetesingress.allowEmptyServices=true"
|
isDefaultClass: false
|
||||||
- "--providers.kubernetesingress.ingressendpoint.publishedservice=traefik-system/traefik"
|
providers:
|
||||||
|
kubernetesCRD:
|
||||||
|
enabled: true
|
||||||
|
allowEmptyServices: true
|
||||||
|
kubernetesIngress:
|
||||||
|
enabled: true
|
||||||
|
allowEmptyServices: true
|
||||||
|
publishedService:
|
||||||
|
enabled: true
|
||||||
ingressRoute:
|
ingressRoute:
|
||||||
dashboard:
|
dashboard:
|
||||||
enabled: false
|
enabled: false
|
||||||
rbac:
|
logs:
|
||||||
enabled: true
|
general:
|
||||||
service:
|
level: INFO
|
||||||
type: LoadBalancer
|
|
||||||
annotations:
|
|
||||||
metallb.io/loadBalancerIPs: "10.0.57.202"
|
|
||||||
metallb.io/address-pool: "prod-pool"
|
|
||||||
ports:
|
|
||||||
web:
|
|
||||||
port: 8000
|
|
||||||
exposedPort: 80
|
|
||||||
protocol: TCP
|
|
||||||
websecure:
|
|
||||||
port: 8443
|
|
||||||
exposedPort: 443
|
|
||||||
protocol: TCP
|
|
||||||
tls:
|
|
||||||
enabled: true
|
|
||||||
irc:
|
|
||||||
port: 6667
|
|
||||||
exposedPort: 6667
|
|
||||||
protocol: TCP
|
|
||||||
expose:
|
|
||||||
default: true
|
|
||||||
irctls:
|
|
||||||
port: 6697
|
|
||||||
exposedPort: 6697
|
|
||||||
protocol: TCP
|
|
||||||
expose:
|
|
||||||
default: true
|
|
||||||
traefik:
|
|
||||||
port: 8080
|
|
||||||
exposedPort: 8080
|
|
||||||
protocol: TCP
|
|
||||||
expose:
|
|
||||||
default: false
|
|
||||||
metrics:
|
|
||||||
port: 9100
|
|
||||||
exposedPort: 9100
|
|
||||||
protocol: TCP
|
|
||||||
expose:
|
|
||||||
default: false
|
|
||||||
metrics:
|
|
||||||
prometheus:
|
|
||||||
entryPoint: metrics
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: "100m"
|
|
||||||
memory: "128Mi"
|
|
||||||
limits:
|
|
||||||
cpu: "500m"
|
|
||||||
memory: "256Mi"
|
|
||||||
tolerations:
|
|
||||||
- key: "node-role.kubernetes.io/control-plane"
|
|
||||||
operator: "Exists"
|
|
||||||
effect: "NoSchedule"
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ public sealed class FleetManifestLintTests
|
|||||||
{
|
{
|
||||||
"brochure.flowercore.io",
|
"brochure.flowercore.io",
|
||||||
"dist.flowercore.io",
|
"dist.flowercore.io",
|
||||||
|
"update.flowercore.io",
|
||||||
|
"updates.flowercore.io",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hosts that allow a tightly bounded write surface in addition to GET/HEAD.
|
// Hosts that allow a tightly bounded write surface in addition to GET/HEAD.
|
||||||
@@ -247,6 +249,22 @@ public sealed class FleetManifestLintTests
|
|||||||
violations.Should().BeEmpty();
|
violations.Should().BeEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Gx10PublicLoadBalancers_MustPreserveClientSourceIp()
|
||||||
|
{
|
||||||
|
var traefikPath = Path.Combine(Inventory.BluejayRoot, "gx10", "platform", "traefik-helmchart.yaml");
|
||||||
|
var traefik = File.ReadAllText(traefikPath);
|
||||||
|
|
||||||
|
traefik.Should().Contain("metallb.io/loadBalancerIPs: 10.0.57.202");
|
||||||
|
traefik.Should().Contain("spec:\n externalTrafficPolicy: Local");
|
||||||
|
|
||||||
|
var giteaPath = Path.Combine(Inventory.BluejayRoot, "gx10", "platform", "gitea-ssh-service.yaml");
|
||||||
|
var gitea = File.ReadAllText(giteaPath);
|
||||||
|
|
||||||
|
gitea.Should().Contain("metallb.io/loadBalancerIPs: 10.0.57.206");
|
||||||
|
gitea.Should().Contain("externalTrafficPolicy: Local");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ApiKeyProtectedDeployments_MustUseTcpSocketHealthProbes()
|
public void ApiKeyProtectedDeployments_MustUseTcpSocketHealthProbes()
|
||||||
{
|
{
|
||||||
@@ -981,6 +999,32 @@ public sealed class FleetManifestLintTests
|
|||||||
gatewayManifest.Should().Contain("port: 5400");
|
gatewayManifest.Should().Contain("port: 5400");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Gx10DeviceManagementWriteApis_RequireRuntimeBackedOperatorAuth()
|
||||||
|
{
|
||||||
|
var web = Gx10DeploymentContainer("fc-devicemgmt", "deployment-fc-devicemgmt-web.json");
|
||||||
|
|
||||||
|
JsonEnvValue(web, "FlowerCore__Auth__Enabled").Should().Be("true");
|
||||||
|
JsonEnvSecretName(web, "Auth__ApiKey").Should().Be("fc-devicemgmt-runtime");
|
||||||
|
JsonEnvSecretKey(web, "Auth__ApiKey").Should().Be("DEVICE_MANAGEMENT_OPERATOR_API_KEY");
|
||||||
|
JsonEnvSecretOptional(web, "Auth__ApiKey").Should().BeNull();
|
||||||
|
JsonEnvSecretName(web, "FlowerCore__Auth__ApiKey").Should().Be("fc-devicemgmt-runtime");
|
||||||
|
JsonEnvSecretKey(web, "FlowerCore__Auth__ApiKey").Should().Be("DEVICE_MANAGEMENT_OPERATOR_API_KEY");
|
||||||
|
JsonEnvSecretOptional(web, "FlowerCore__Auth__ApiKey").Should().BeNull();
|
||||||
|
JsonEnvSecretName(web, "Auth__AdminApiKey").Should().Be("fc-devicemgmt-runtime");
|
||||||
|
JsonEnvSecretKey(web, "Auth__AdminApiKey").Should().Be("DEVICE_MANAGEMENT_ADMIN_API_KEY");
|
||||||
|
JsonEnvSecretOptional(web, "Auth__AdminApiKey").Should().BeNull();
|
||||||
|
JsonEnvSecretName(web, "FlowerCore__Auth__AdminApiKey").Should().Be("fc-devicemgmt-runtime");
|
||||||
|
JsonEnvSecretKey(web, "FlowerCore__Auth__AdminApiKey").Should().Be("DEVICE_MANAGEMENT_ADMIN_API_KEY");
|
||||||
|
JsonEnvSecretOptional(web, "FlowerCore__Auth__AdminApiKey").Should().BeNull();
|
||||||
|
JsonEnvSecretName(web, "FlowerCore__DeviceManagement__EnrollmentCertificateAuthorityCertificatePem").Should().Be("fc-devicemgmt-runtime");
|
||||||
|
JsonEnvSecretKey(web, "FlowerCore__DeviceManagement__EnrollmentCertificateAuthorityCertificatePem").Should().Be("DEVICE_MANAGEMENT_ENROLLMENT_CA_CERTIFICATE_PEM");
|
||||||
|
JsonEnvSecretOptional(web, "FlowerCore__DeviceManagement__EnrollmentCertificateAuthorityCertificatePem").Should().BeTrue();
|
||||||
|
JsonEnvSecretName(web, "FlowerCore__DeviceManagement__EnrollmentCertificateAuthorityPrivateKeyPem").Should().Be("fc-devicemgmt-runtime");
|
||||||
|
JsonEnvSecretKey(web, "FlowerCore__DeviceManagement__EnrollmentCertificateAuthorityPrivateKeyPem").Should().Be("DEVICE_MANAGEMENT_ENROLLMENT_CA_PRIVATE_KEY_PEM");
|
||||||
|
JsonEnvSecretOptional(web, "FlowerCore__DeviceManagement__EnrollmentCertificateAuthorityPrivateKeyPem").Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Gx10PhpTenantRoutes_HaveEdgeControlSubstrate()
|
public void Gx10PhpTenantRoutes_HaveEdgeControlSubstrate()
|
||||||
{
|
{
|
||||||
@@ -1071,6 +1115,120 @@ public sealed class FleetManifestLintTests
|
|||||||
serviceRef.GetProperty("port").GetInt32().Should().Be(8080);
|
serviceRef.GetProperty("port").GetInt32().Should().Be(8080);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Gx10BluejayDevTenantRoute_IsFrontedByOwaspCrsWaf()
|
||||||
|
{
|
||||||
|
var appRoot = Path.Combine(Inventory.BluejayRoot, "apps-gx10", "fc-tenant-andrew");
|
||||||
|
var wafContainer = Gx10DeploymentContainer("fc-tenant-andrew", "deployment-andrew-web-waf.json");
|
||||||
|
wafContainer.GetProperty("image").GetString()
|
||||||
|
.Should()
|
||||||
|
.Be("owasp/modsecurity-crs:4.25-nginx-alpine-lts@sha256:88b59911549723e71beabf3b4aa47bbd31b00e79401f442e65ddfc430ae46343");
|
||||||
|
JsonEnvValue(wafContainer, "BACKEND").Should().Be("http://andrew-web.fc-tenant-andrew.svc.cluster.local:80");
|
||||||
|
JsonEnvValue(wafContainer, "SERVER_NAME").Should().Be("bluejay.dev www.bluejay.dev");
|
||||||
|
JsonEnvValue(wafContainer, "MODSEC_RULE_ENGINE").Should().Be("On");
|
||||||
|
JsonEnvValue(wafContainer, "MODSEC_AUDIT_ENGINE").Should().Be("RelevantOnly");
|
||||||
|
JsonEnvValue(wafContainer, "MODSEC_AUDIT_LOG").Should().Be("/dev/stdout");
|
||||||
|
|
||||||
|
using var wafDeployment = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "deployment-andrew-web-waf.json")));
|
||||||
|
var podSpec = wafDeployment.RootElement
|
||||||
|
.GetProperty("spec")
|
||||||
|
.GetProperty("template")
|
||||||
|
.GetProperty("spec");
|
||||||
|
podSpec.GetProperty("enableServiceLinks").GetBoolean().Should().BeFalse();
|
||||||
|
podSpec.GetProperty("securityContext").GetProperty("runAsUser").GetInt32().Should().Be(101);
|
||||||
|
podSpec.GetProperty("securityContext").GetProperty("runAsNonRoot").GetBoolean().Should().BeTrue();
|
||||||
|
wafContainer.GetProperty("readinessProbe")
|
||||||
|
.GetProperty("httpGet")
|
||||||
|
.GetProperty("httpHeaders")[0]
|
||||||
|
.GetProperty("value")
|
||||||
|
.GetString()
|
||||||
|
.Should()
|
||||||
|
.Be("bluejay.dev");
|
||||||
|
|
||||||
|
using var service = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "service-andrew-web-waf.json")));
|
||||||
|
service.RootElement.GetProperty("spec").GetProperty("selector").GetProperty("app.kubernetes.io/name").GetString().Should().Be("andrew-web-waf");
|
||||||
|
var servicePort = service.RootElement.GetProperty("spec").GetProperty("ports").EnumerateArray().Should().ContainSingle().Subject;
|
||||||
|
servicePort.GetProperty("port").GetInt32().Should().Be(8080);
|
||||||
|
servicePort.GetProperty("targetPort").GetInt32().Should().Be(8080);
|
||||||
|
|
||||||
|
using var ingressRoute = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "ingressroute-andrew-web.json")));
|
||||||
|
var route = ingressRoute.RootElement
|
||||||
|
.GetProperty("spec")
|
||||||
|
.GetProperty("routes")[0];
|
||||||
|
var serviceRef = route
|
||||||
|
.GetProperty("services")
|
||||||
|
.EnumerateArray()
|
||||||
|
.Should()
|
||||||
|
.ContainSingle()
|
||||||
|
.Subject;
|
||||||
|
serviceRef.GetProperty("name").GetString().Should().Be("andrew-web-waf");
|
||||||
|
serviceRef.GetProperty("port").GetInt32().Should().Be(8080);
|
||||||
|
|
||||||
|
route.GetProperty("middlewares")
|
||||||
|
.EnumerateArray()
|
||||||
|
.Select(item => item.GetProperty("name").GetString())
|
||||||
|
.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();
|
||||||
|
headerSpec.GetProperty("stsSeconds").GetInt32().Should().Be(31536000);
|
||||||
|
|
||||||
|
using var tlsOption = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "tlsoption-andrew-tenant-tls13.json")));
|
||||||
|
tlsOption.RootElement.GetProperty("spec").GetProperty("minVersion").GetString().Should().Be("VersionTLS13");
|
||||||
|
ingressRoute.RootElement
|
||||||
|
.GetProperty("spec")
|
||||||
|
.GetProperty("tls")
|
||||||
|
.GetProperty("options")
|
||||||
|
.GetProperty("name")
|
||||||
|
.GetString()
|
||||||
|
.Should()
|
||||||
|
.Be("andrew-tenant-tls13");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Gx10HostingManagers_ProvisioningCrdsAndRbacMustBeGitOpsOwned()
|
public void Gx10HostingManagers_ProvisioningCrdsAndRbacMustBeGitOpsOwned()
|
||||||
{
|
{
|
||||||
@@ -1172,6 +1330,39 @@ public sealed class FleetManifestLintTests
|
|||||||
match.Should().NotContain("Method(`POST`)");
|
match.Should().NotContain("Method(`POST`)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UpdateCenterPublicIngress_KeepsDeliveryOnlyGetHeadMethodAllowlist()
|
||||||
|
{
|
||||||
|
var publicIngress = AppDocuments("fc-updater")
|
||||||
|
.Single(document => document.Kind == "IngressRoute" && document.Name == "updatecenter-web-public");
|
||||||
|
var route = publicIngress.MappingSequence("spec", "routes").Should().ContainSingle().Subject;
|
||||||
|
var match = ManifestNodeExtensions.Scalar(route, "match");
|
||||||
|
|
||||||
|
match.Should().Contain("Host(`update.flowercore.io`)");
|
||||||
|
match.Should().Contain("Host(`updates.flowercore.io`)");
|
||||||
|
match.Should().Contain("Method(`GET`)");
|
||||||
|
match.Should().Contain("Method(`HEAD`)");
|
||||||
|
match.Should().NotContain("Method(`POST`)");
|
||||||
|
match.Should().NotContain("Method(`OPTIONS`)");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Gx10UpdateCenterPublicIngress_StaysGetHeadOnlyAndUsesContainmentImage()
|
||||||
|
{
|
||||||
|
var appRoot = Path.Combine(Inventory.BluejayRoot, "apps-gx10", "fc-updater");
|
||||||
|
var publicRoute = File.ReadAllText(Path.Combine(appRoot, "ingressroute-updatecenter-web-public-gx10.json"));
|
||||||
|
var deployment = File.ReadAllText(Path.Combine(appRoot, "deployment-updatecenter-web.json"));
|
||||||
|
|
||||||
|
publicRoute.Should().Contain("Host(`update.flowercore.io`)");
|
||||||
|
publicRoute.Should().Contain("Host(`updates.flowercore.io`)");
|
||||||
|
publicRoute.Should().Contain("Method(`GET`)");
|
||||||
|
publicRoute.Should().Contain("Method(`HEAD`)");
|
||||||
|
publicRoute.Should().NotContain("Method(`POST`)");
|
||||||
|
publicRoute.Should().NotContain("Method(`OPTIONS`)");
|
||||||
|
deployment.Should().Contain("localhost/fc-updater-web:v");
|
||||||
|
deployment.Should().NotContain("localhost/fc-updater-web:v20260614-regroup-bdf4a4a");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void DnsAndMediaIngressRoutes_MatchLiveInternalHosts()
|
public void DnsAndMediaIngressRoutes_MatchLiveInternalHosts()
|
||||||
{
|
{
|
||||||
@@ -1283,9 +1474,13 @@ public sealed class FleetManifestLintTests
|
|||||||
|
|
||||||
private static bool? JsonEnvSecretOptional(JsonElement container, string name)
|
private static bool? JsonEnvSecretOptional(JsonElement container, string name)
|
||||||
{
|
{
|
||||||
return JsonEnvMapping(container, name) is { } env
|
if (JsonEnvMapping(container, name) is not { } env)
|
||||||
? env.GetProperty("valueFrom").GetProperty("secretKeyRef").GetProperty("optional").GetBoolean()
|
{
|
||||||
: null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var secretKeyRef = env.GetProperty("valueFrom").GetProperty("secretKeyRef");
|
||||||
|
return secretKeyRef.TryGetProperty("optional", out var optional) ? optional.GetBoolean() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? JsonEnvValue(JsonElement container, string name)
|
private static string? JsonEnvValue(JsonElement container, string name)
|
||||||
|
|||||||
208
tests/bluejay-infra-lint/Gx10AppleMdmNanohubTests.cs
Normal file
208
tests/bluejay-infra-lint/Gx10AppleMdmNanohubTests.cs
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
using YamlDotNet.RepresentationModel;
|
||||||
|
|
||||||
|
namespace BluejayInfraLint.Tests;
|
||||||
|
|
||||||
|
[Trait("Category", "Unit")]
|
||||||
|
public sealed class Gx10AppleMdmNanohubTests
|
||||||
|
{
|
||||||
|
private static readonly string Root = FindRepoRoot();
|
||||||
|
private static readonly string AppRoot = Path.Combine(Root, "apps-gx10", "fc-apple-mdm");
|
||||||
|
private static readonly IReadOnlyList<YamlMappingNode> Documents = LoadDocuments();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Manifest_DeclaresLockedDownNanoHubRuntime()
|
||||||
|
{
|
||||||
|
Documents.Should().Contain(document => Is(document, "Namespace", "fc-apple-mdm"));
|
||||||
|
Documents.Should().Contain(document => Is(document, "ConfigMap", "fc-apple-mdm-root-ca"));
|
||||||
|
Documents.Should().Contain(document => Is(document, "Service", "fc-apple-mdm"));
|
||||||
|
Documents.Should().Contain(document => Is(document, "Service", "fc-apple-mdm-scep"));
|
||||||
|
Documents.Should().Contain(document => Is(document, "EndpointSlice", "fc-apple-mdm-scep-noc1"));
|
||||||
|
Documents.Should().Contain(document => Is(document, "NetworkPolicy", "fc-apple-mdm-netpol"));
|
||||||
|
Documents.Should().NotContain(document => (document.Scalar("kind") ?? string.Empty) == "Secret");
|
||||||
|
Documents.Should().NotContain(document => (document.Scalar("kind") ?? string.Empty) == "OnePasswordItem");
|
||||||
|
|
||||||
|
var pvc = Single("PersistentVolumeClaim", "fc-apple-mdm-data");
|
||||||
|
pvc.Scalar("spec", "storageClassName").Should().Be("local-path");
|
||||||
|
pvc.Scalar("spec", "resources", "requests", "storage").Should().Be("2Gi");
|
||||||
|
|
||||||
|
var deployment = Single("Deployment", "fc-apple-mdm");
|
||||||
|
deployment.Scalar("spec", "strategy", "type").Should().Be("Recreate");
|
||||||
|
deployment.Scalar("spec", "template", "metadata", "annotations", "fc.flowercore.io/probe-path").Should().Be("/version");
|
||||||
|
deployment.Scalar("spec", "template", "metadata", "annotations", "flowercore.io/root-ca-sha256")
|
||||||
|
.Should()
|
||||||
|
.Be("a9120c88fa3ec735d790aa4cfeb61ac2946730338969015bebaccc08fe10535e");
|
||||||
|
|
||||||
|
var maybePodSpec = deployment.Mapping("spec", "template", "spec");
|
||||||
|
maybePodSpec.Should().NotBeNull();
|
||||||
|
var podSpec = maybePodSpec!;
|
||||||
|
podSpec.Scalar("enableServiceLinks").Should().Be("false");
|
||||||
|
podSpec.Scalar("securityContext", "runAsUser").Should().Be("1654");
|
||||||
|
podSpec.Scalar("securityContext", "runAsNonRoot").Should().Be("true");
|
||||||
|
|
||||||
|
var container = podSpec.MappingSequence("containers").Should().ContainSingle().Subject;
|
||||||
|
container.Scalar("name").Should().Be("nanohub");
|
||||||
|
container.Scalar("image").Should().Be("localhost/fc-apple-mdm-nanohub:v0.2.0-20260617");
|
||||||
|
container.Scalar("imagePullPolicy").Should().Be("Never");
|
||||||
|
container.Scalar("securityContext", "readOnlyRootFilesystem").Should().Be("true");
|
||||||
|
container.Scalar("securityContext", "allowPrivilegeEscalation").Should().Be("false");
|
||||||
|
|
||||||
|
EnvValue(container, "NANOHUB_LISTEN").Should().Be(":9004");
|
||||||
|
EnvValue(container, "NANOHUB_STORAGE").Should().Be("file");
|
||||||
|
EnvValue(container, "NANOHUB_STORAGE_DSN").Should().Be("/var/lib/nanohub/db");
|
||||||
|
EnvValue(container, "NANOHUB_CHECKIN").Should().Be("true");
|
||||||
|
EnvValue(container, "NANOHUB_CA").Should().Be("/etc/nanohub/ca/root_ca.crt");
|
||||||
|
EnvSecretName(container, "NANOHUB_API_KEY").Should().Be("fc-apple-mdm-runtime");
|
||||||
|
EnvSecretKey(container, "NANOHUB_API_KEY").Should().Be("NANOHUB_API_KEY");
|
||||||
|
EnvSecretName(container, "NANOHUB_WEBHOOK_URL").Should().Be("fc-apple-mdm-runtime");
|
||||||
|
EnvSecretKey(container, "NANOHUB_WEBHOOK_URL").Should().Be("NANOHUB_WEBHOOK_URL");
|
||||||
|
EnvSecretOptional(container, "NANOHUB_WEBHOOK_URL").Should().Be("true");
|
||||||
|
|
||||||
|
VolumeMount(container, "data").Scalar("mountPath").Should().Be("/var/lib/nanohub");
|
||||||
|
VolumeMount(container, "root-ca").Scalar("mountPath").Should().Be("/etc/nanohub/ca");
|
||||||
|
VolumeMount(container, "root-ca").Scalar("readOnly").Should().Be("true");
|
||||||
|
ProbePath(container, "startupProbe").Should().Be("/version");
|
||||||
|
ProbePath(container, "readinessProbe").Should().Be("/version");
|
||||||
|
container.Scalar("livenessProbe", "tcpSocket", "port").Should().Be("9004");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Manifest_ExposesOnlyMdmCheckinVersionAndScepPaths()
|
||||||
|
{
|
||||||
|
var certificate = Single("Certificate", "fc-apple-mdm-tls");
|
||||||
|
certificate.Scalar("spec", "issuerRef", "name").Should().Be("step-ca-acme");
|
||||||
|
certificate.Scalar("spec", "issuerRef", "kind").Should().Be("ClusterIssuer");
|
||||||
|
certificate.ScalarSequence("spec", "dnsNames").Should().ContainSingle("mdm.iamworkin.lan");
|
||||||
|
|
||||||
|
var scepService = Single("Service", "fc-apple-mdm-scep");
|
||||||
|
scepService.Scalar("spec", "type").Should().Be("ClusterIP");
|
||||||
|
var scepServicePort = scepService.MappingSequence("spec", "ports").Should().ContainSingle().Subject;
|
||||||
|
scepServicePort.Scalar("name").Should().Be("http");
|
||||||
|
scepServicePort.Scalar("port").Should().Be("80");
|
||||||
|
scepServicePort.Scalar("targetPort").Should().Be("9080");
|
||||||
|
|
||||||
|
var scepEndpointSlice = Single("EndpointSlice", "fc-apple-mdm-scep-noc1");
|
||||||
|
scepEndpointSlice.Scalar("addressType").Should().Be("IPv4");
|
||||||
|
scepEndpointSlice.Scalar("metadata", "labels", "kubernetes.io/service-name").Should().Be("fc-apple-mdm-scep");
|
||||||
|
var scepEndpoint = scepEndpointSlice.MappingSequence("endpoints").Should().ContainSingle().Subject;
|
||||||
|
scepEndpoint.ScalarSequence("addresses").Should().ContainSingle("10.0.56.10");
|
||||||
|
var scepEndpointPort = scepEndpointSlice.MappingSequence("ports").Should().ContainSingle().Subject;
|
||||||
|
scepEndpointPort.Scalar("name").Should().Be("http");
|
||||||
|
scepEndpointPort.Scalar("port").Should().Be("9080");
|
||||||
|
|
||||||
|
var ingress = Single("IngressRoute", "fc-apple-mdm");
|
||||||
|
var routes = ingress.MappingSequence("spec", "routes");
|
||||||
|
routes.Should().HaveCount(2);
|
||||||
|
var scepRoute = routes.Single(route => route.Scalar("match")?.Contains("PathPrefix(`/scep`)") == true);
|
||||||
|
var nanohubRoute = routes.Single(route => route.Scalar("match")?.Contains("PathPrefix(`/mdm`)") == true);
|
||||||
|
var match = nanohubRoute.Scalar("match");
|
||||||
|
|
||||||
|
match.Should().Contain("Host(`mdm.iamworkin.lan`)");
|
||||||
|
match.Should().Contain("PathPrefix(`/mdm`)");
|
||||||
|
match.Should().Contain("PathPrefix(`/checkin`)");
|
||||||
|
match.Should().Contain("PathPrefix(`/version`)");
|
||||||
|
match.Should().NotContain("/api/v1");
|
||||||
|
match.Should().NotContain("PathPrefix(`/api`)");
|
||||||
|
|
||||||
|
scepRoute.Scalar("match").Should().Contain("Host(`mdm.iamworkin.lan`)");
|
||||||
|
scepRoute.Scalar("match").Should().Contain("PathPrefix(`/scep`)");
|
||||||
|
var scepRouteService = scepRoute.MappingSequence("services").Should().ContainSingle().Subject;
|
||||||
|
scepRouteService.Scalar("name").Should().Be("fc-apple-mdm-scep");
|
||||||
|
scepRouteService.Scalar("port").Should().Be("80");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Readme_DocumentsSecretImportAndSupportBoundary()
|
||||||
|
{
|
||||||
|
var readme = File.ReadAllText(Path.Combine(AppRoot, "README.md"));
|
||||||
|
|
||||||
|
readme.Should().Contain("FlowerCore Apple MDM Runtime");
|
||||||
|
readme.Should().Contain("Secret/fc-apple-mdm-runtime");
|
||||||
|
readme.Should().Contain("imagePullPolicy: Never");
|
||||||
|
readme.Should().Contain("10.0.57.202");
|
||||||
|
readme.Should().Contain("https://mdm.iamworkin.lan/scep/apple-mdm-scep");
|
||||||
|
readme.Should().Contain("Smallstep SCEP requires an RSA intermediate");
|
||||||
|
readme.Should().Contain("does not create an APNs MDM push certificate");
|
||||||
|
readme.Should().Contain("managed Wi-Fi payload");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static YamlMappingNode Single(string kind, string name)
|
||||||
|
{
|
||||||
|
return Documents.Single(document => Is(document, kind, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool Is(YamlMappingNode document, string kind, string name)
|
||||||
|
{
|
||||||
|
return document.Scalar("kind") == kind
|
||||||
|
&& document.Scalar("metadata", "name") == name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? EnvValue(YamlMappingNode container, string name)
|
||||||
|
{
|
||||||
|
return EnvMapping(container, name)?.Scalar("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? EnvSecretName(YamlMappingNode container, string name)
|
||||||
|
{
|
||||||
|
return EnvMapping(container, name)?.Scalar("valueFrom", "secretKeyRef", "name");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? EnvSecretKey(YamlMappingNode container, string name)
|
||||||
|
{
|
||||||
|
return EnvMapping(container, name)?.Scalar("valueFrom", "secretKeyRef", "key");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? EnvSecretOptional(YamlMappingNode container, string name)
|
||||||
|
{
|
||||||
|
return EnvMapping(container, name)?.Scalar("valueFrom", "secretKeyRef", "optional");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ProbePath(YamlMappingNode container, string probeKey)
|
||||||
|
{
|
||||||
|
return container.Scalar(probeKey, "httpGet", "path");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static YamlMappingNode VolumeMount(YamlMappingNode container, string name)
|
||||||
|
{
|
||||||
|
return container.MappingSequence("volumeMounts")
|
||||||
|
.Single(mount => mount.Scalar("name") == name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static YamlMappingNode? EnvMapping(YamlMappingNode container, string name)
|
||||||
|
{
|
||||||
|
return container.MappingSequence("env")
|
||||||
|
.SingleOrDefault(env => env.Scalar("name") == name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<YamlMappingNode> LoadDocuments()
|
||||||
|
{
|
||||||
|
var stream = new YamlStream();
|
||||||
|
using var reader = File.OpenText(Path.Combine(AppRoot, "fc-apple-mdm.yaml"));
|
||||||
|
stream.Load(reader);
|
||||||
|
|
||||||
|
return stream.Documents
|
||||||
|
.Select(document => document.RootNode)
|
||||||
|
.OfType<YamlMappingNode>()
|
||||||
|
.Where(mapping => !string.IsNullOrWhiteSpace(mapping.Scalar("kind")))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FindRepoRoot()
|
||||||
|
{
|
||||||
|
var current = new DirectoryInfo(AppContext.BaseDirectory);
|
||||||
|
while (current is not null)
|
||||||
|
{
|
||||||
|
if (Directory.Exists(Path.Combine(current.FullName, "apps-gx10"))
|
||||||
|
&& Directory.Exists(Path.Combine(current.FullName, "tests"))
|
||||||
|
&& File.Exists(Path.Combine(current.FullName, "README.md")))
|
||||||
|
{
|
||||||
|
return current.FullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new DirectoryNotFoundException("Could not find bluejay-infra root.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
package bluejayinfra.public_method_allowlist
|
package bluejayinfra.public_method_allowlist
|
||||||
|
|
||||||
public_hosts := {"brochure.flowercore.io", "dist.flowercore.io", "dns.iamworkin.lan"}
|
public_hosts := {
|
||||||
|
"brochure.flowercore.io",
|
||||||
|
"dist.flowercore.io",
|
||||||
|
"dns.iamworkin.lan",
|
||||||
|
"update.flowercore.io",
|
||||||
|
"updates.flowercore.io",
|
||||||
|
}
|
||||||
|
|
||||||
deny[msg] {
|
deny[msg] {
|
||||||
input.kind == "IngressRoute"
|
input.kind == "IngressRoute"
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ package bluejayinfra.public_readwrite_allowlist
|
|||||||
public_readwrite_hosts := {
|
public_readwrite_hosts := {
|
||||||
"updatecenter.iamworkin.lan",
|
"updatecenter.iamworkin.lan",
|
||||||
"updates.iamworkin.lan",
|
"updates.iamworkin.lan",
|
||||||
"update.flowercore.io",
|
|
||||||
"updates.flowercore.io",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required_methods := {"GET", "HEAD", "POST", "OPTIONS"}
|
required_methods := {"GET", "HEAD", "POST", "OPTIONS"}
|
||||||
|
|||||||
Reference in New Issue
Block a user