Compare commits
1 Commits
feat/authe
...
211ecbf294
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
211ecbf294 |
@@ -1,18 +0,0 @@
|
|||||||
# Authentik OIDC client registration sweep
|
|
||||||
|
|
||||||
This directory holds the FlowerCore per-service OIDC client secret references
|
|
||||||
for the ADR-093 / ADR-124 Phase 1 step 8 sweep.
|
|
||||||
|
|
||||||
The `clients/*-oidc-client.yaml` manifests are intentionally only
|
|
||||||
`OnePasswordItem` CRDs. The actual 1Password items are created by an operator in
|
|
||||||
the `IAmWorkin` vault with these fields:
|
|
||||||
|
|
||||||
| Field | Purpose |
|
|
||||||
| --- | --- |
|
|
||||||
| `client_id` | Authentik provider client id, default `<slug>` |
|
|
||||||
| `client_secret` | Authentik provider client secret |
|
|
||||||
| `issuer_url` | `https://id.iamworkin.lan/application/o/<slug>/` |
|
|
||||||
|
|
||||||
Run `scripts/authentik-bulk-client-create.py` in dry-run mode first. Live REST
|
|
||||||
mutation requires `--apply`, `AUTHENTIK_TOKEN`, and an operator-provided
|
|
||||||
client-secret JSON file. The script redacts secrets in all normal output.
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: aistation-oidc-client
|
|
||||||
namespace: fc-aistation
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: aistation
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/aistation-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/aistation-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: audit-oidc-client
|
|
||||||
namespace: fc-audit
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: audit
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/audit-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/audit-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: chat-oidc-client
|
|
||||||
namespace: fc-chat
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: chat
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/chat-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/chat-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: distribution-oidc-client
|
|
||||||
namespace: fc-distribution
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: distribution
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/distribution-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/distribution-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: dms-oidc-client
|
|
||||||
namespace: fc-dms
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: dms
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/dms-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/dms-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: dns-oidc-client
|
|
||||||
namespace: fc-dns
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: dns
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/dns-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/dns-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: intranet-oidc-client
|
|
||||||
namespace: intranet
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: intranet
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/intranet-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/intranet-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: irc-oidc-client
|
|
||||||
namespace: irc
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: irc
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/irc-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/irc-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: kiosk-oidc-client
|
|
||||||
namespace: fc-system
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: kiosk
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/kiosk-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/kiosk-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: knowledge-oidc-client
|
|
||||||
namespace: knowledge
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: knowledge
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/knowledge-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/knowledge-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: library-oidc-client
|
|
||||||
namespace: fc-library
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: library
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/library-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/library-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: licensing-oidc-client
|
|
||||||
namespace: fc-licensing
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: licensing
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/licensing-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/licensing-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: llmbridge-oidc-client
|
|
||||||
namespace: fc-llm-bridge
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: llmbridge
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/llmbridge-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/llmbridge-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: media-oidc-client
|
|
||||||
namespace: fc-media
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: media
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/media-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/media-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: menuboard-oidc-client
|
|
||||||
namespace: fc-menuboard
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: menuboard
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/menuboard-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/menuboard-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: messageboard-oidc-client
|
|
||||||
namespace: fc-messageboard
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: messageboard
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/messageboard-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/messageboard-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: mike-bundle-oidc-client
|
|
||||||
namespace: fc-mike-bundle
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: mike-bundle
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/mike-bundle-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/mike-bundle-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: mndot-oidc-client
|
|
||||||
namespace: fc-mndot
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: mndot
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/mndot-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/mndot-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: mysql-oidc-client
|
|
||||||
namespace: fc-mysql
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: mysql
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/mysql-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/mysql-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: php-oidc-client
|
|
||||||
namespace: fc-php
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: php
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/php-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/php-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: pimanager-oidc-client
|
|
||||||
namespace: fc-pimanager
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: pimanager
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/pimanager-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/pimanager-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: presentations-oidc-client
|
|
||||||
namespace: fc-presentations
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: presentations
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/presentations-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/presentations-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: print-oidc-client
|
|
||||||
namespace: fc-print
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: print
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/print-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/print-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: provisioning-oidc-client
|
|
||||||
namespace: fc-provisioning
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: provisioning
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/provisioning-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/provisioning-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: remotedesktop-oidc-client
|
|
||||||
namespace: fc-desktop
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: remotedesktop
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/remotedesktop-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/remotedesktop-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: retail-oidc-client
|
|
||||||
namespace: fc-retail
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: retail
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/retail-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/retail-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: scoreboards-oidc-client
|
|
||||||
namespace: fc-scoreboard
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: scoreboards
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/scoreboards-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/scoreboards-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: segmentdisplay-oidc-client
|
|
||||||
namespace: fc-segmentdisplay
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: segmentdisplay
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/segmentdisplay-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/segmentdisplay-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: signage-oidc-client
|
|
||||||
namespace: fc-signage
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: signage
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/signage-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/signage-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: signalcontrol-oidc-client
|
|
||||||
namespace: fc-signalcontrol
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: signalcontrol
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/signalcontrol-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/signalcontrol-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: telephony-oidc-client
|
|
||||||
namespace: telephony
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: telephony
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/telephony-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/telephony-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: ttsreader-oidc-client
|
|
||||||
namespace: fc-ttsreader
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: ttsreader
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/ttsreader-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/ttsreader-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: worldbuilder-oidc-client
|
|
||||||
namespace: fc-worldbuilder
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: worldbuilder
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/worldbuilder-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/worldbuilder-oidc-client"
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# ArgoCD's bluejay-infra ApplicationSet sees apps/authentik as one app. Keep
|
|
||||||
# an explicit resource list so the client manifests can live under clients/.
|
|
||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
||||||
kind: Kustomization
|
|
||||||
resources:
|
|
||||||
- clients/library-oidc-client.yaml
|
|
||||||
- clients/retail-oidc-client.yaml
|
|
||||||
- clients/telephony-oidc-client.yaml
|
|
||||||
- clients/knowledge-oidc-client.yaml
|
|
||||||
- clients/llmbridge-oidc-client.yaml
|
|
||||||
- clients/mysql-oidc-client.yaml
|
|
||||||
- clients/php-oidc-client.yaml
|
|
||||||
- clients/signage-oidc-client.yaml
|
|
||||||
- clients/media-oidc-client.yaml
|
|
||||||
- clients/dms-oidc-client.yaml
|
|
||||||
- clients/pimanager-oidc-client.yaml
|
|
||||||
- clients/distribution-oidc-client.yaml
|
|
||||||
- clients/dns-oidc-client.yaml
|
|
||||||
- clients/print-oidc-client.yaml
|
|
||||||
- clients/aistation-oidc-client.yaml
|
|
||||||
- clients/irc-oidc-client.yaml
|
|
||||||
- clients/ttsreader-oidc-client.yaml
|
|
||||||
- clients/chat-oidc-client.yaml
|
|
||||||
- clients/intranet-oidc-client.yaml
|
|
||||||
- clients/remotedesktop-oidc-client.yaml
|
|
||||||
- clients/provisioning-oidc-client.yaml
|
|
||||||
- clients/scoreboards-oidc-client.yaml
|
|
||||||
- clients/mndot-oidc-client.yaml
|
|
||||||
- clients/kiosk-oidc-client.yaml
|
|
||||||
- clients/mike-bundle-oidc-client.yaml
|
|
||||||
- clients/messageboard-oidc-client.yaml
|
|
||||||
- clients/menuboard-oidc-client.yaml
|
|
||||||
- clients/presentations-oidc-client.yaml
|
|
||||||
- clients/segmentdisplay-oidc-client.yaml
|
|
||||||
- clients/signalcontrol-oidc-client.yaml
|
|
||||||
- clients/worldbuilder-oidc-client.yaml
|
|
||||||
- clients/audit-oidc-client.yaml
|
|
||||||
- clients/licensing-oidc-client.yaml
|
|
||||||
26
apps/fc-devicemgmt/1password-item.yaml
Normal file
26
apps/fc-devicemgmt/1password-item.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Runtime secrets for FlowerCore.DeviceManagement.
|
||||||
|
#
|
||||||
|
# OnePasswordItem operator syncs this item into a Kubernetes Secret with the
|
||||||
|
# same name. Expected fields:
|
||||||
|
# DB-Password
|
||||||
|
# mtls-ca.pem
|
||||||
|
# mtls-client.crt
|
||||||
|
# mtls-client.key
|
||||||
|
# mtls-chain.pem
|
||||||
|
#
|
||||||
|
# Do not add literal secret values to this repo. Runtime pods consume the
|
||||||
|
# synced Secret through env vars and read-only mounts.
|
||||||
|
apiVersion: onepassword.com/v1
|
||||||
|
kind: OnePasswordItem
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-runtime
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt
|
||||||
|
app.kubernetes.io/component: secrets
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
spec:
|
||||||
|
itemPath: "vaults/IAmWorkin/items/FlowerCore DeviceManagement Runtime"
|
||||||
33
apps/fc-devicemgmt/argocd-application.yaml
Normal file
33
apps/fc-devicemgmt/argocd-application.yaml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Explicit ArgoCD Application shape for bootstrap/review.
|
||||||
|
#
|
||||||
|
# The live bluejay-infra ApplicationSet already discovers apps/* directories
|
||||||
|
# and creates this same Application name (`infra-fc-devicemgmt`) automatically.
|
||||||
|
# Keep repoURL on the internal Gitea ClusterIP URL; ArgoCD does not trust the
|
||||||
|
# external step-ca HTTPS endpoint.
|
||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: infra-fc-devicemgmt
|
||||||
|
namespace: argocd
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: http://gitea-clusterip.gitea.svc.cluster.local:3000/bluejay/bluejay-infra.git
|
||||||
|
targetRevision: main
|
||||||
|
path: apps/fc-devicemgmt
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
|
- ServerSideApply=true
|
||||||
30
apps/fc-devicemgmt/certificate-web.yaml
Normal file
30
apps/fc-devicemgmt/certificate-web.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Certificate for devices.iamworkin.lan.
|
||||||
|
#
|
||||||
|
# Preflight gate: FlowerCore.DNS / pfSense must contain an explicit A record:
|
||||||
|
# devices.iamworkin.lan -> 10.0.56.200
|
||||||
|
# before this Certificate is synced. step-ca ACME cannot see the CoreDNS
|
||||||
|
# wildcard, so missing pfSense DNS produces cert-manager HTTP-01 backoff
|
||||||
|
# (feedback_pfsense_dns_required_for_acme).
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-web-tls
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-web
|
||||||
|
app.kubernetes.io/component: web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
annotations:
|
||||||
|
flowercore.io/dns-preflight: "devices.iamworkin.lan must resolve to 10.0.56.200 before ACME sync"
|
||||||
|
spec:
|
||||||
|
secretName: fc-devicemgmt-web-tls
|
||||||
|
issuerRef:
|
||||||
|
name: step-ca-acme
|
||||||
|
kind: ClusterIssuer
|
||||||
|
dnsNames:
|
||||||
|
- devices.iamworkin.lan
|
||||||
|
duration: 720h
|
||||||
|
renewBefore: 240h
|
||||||
81
apps/fc-devicemgmt/clusterrole-operator.yaml
Normal file
81
apps/fc-devicemgmt/clusterrole-operator.yaml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-operator
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-operator
|
||||||
|
app.kubernetes.io/component: operator
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- devices.flowercore.io
|
||||||
|
resources:
|
||||||
|
- '*'
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- patch
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- devices.flowercore.io
|
||||||
|
resources:
|
||||||
|
- devices/status
|
||||||
|
- devices/finalizers
|
||||||
|
- devicegroups/status
|
||||||
|
- devicegroups/finalizers
|
||||||
|
- devicepolicies/status
|
||||||
|
- devicepolicies/finalizers
|
||||||
|
- remotecommands/status
|
||||||
|
- remotecommands/finalizers
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- update
|
||||||
|
- patch
|
||||||
|
- apiGroups:
|
||||||
|
- apps
|
||||||
|
resources:
|
||||||
|
- deployments
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- pods
|
||||||
|
- services
|
||||||
|
- configmaps
|
||||||
|
- secrets
|
||||||
|
- events
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- patch
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- batch
|
||||||
|
resources:
|
||||||
|
- jobs
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- patch
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- networking.k8s.io
|
||||||
|
resources:
|
||||||
|
- networkpolicies
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
19
apps/fc-devicemgmt/clusterrolebinding-operator.yaml
Normal file
19
apps/fc-devicemgmt/clusterrolebinding-operator.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-operator
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-operator
|
||||||
|
app.kubernetes.io/component: operator
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: fc-devicemgmt-operator
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: fc-devicemgmt-operator
|
||||||
|
namespace: fc-devicemgmt
|
||||||
109
apps/fc-devicemgmt/deployment-operator.yaml
Normal file
109
apps/fc-devicemgmt/deployment-operator.yaml
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# FlowerCore.DeviceManagement Operator.
|
||||||
|
#
|
||||||
|
# KubeOps controller for devices.flowercore.io resources. Operator-created
|
||||||
|
# children must set OwnerReferences + traceability labels/annotations per
|
||||||
|
# k8s-pod-ownership-and-traceability-standard.md. RBAC below grants
|
||||||
|
# apps/deployments/get so the process can resolve its own Deployment UID.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-operator
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app: fc-devicemgmt-operator
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-operator
|
||||||
|
app.kubernetes.io/component: operator
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
annotations:
|
||||||
|
flowercore.io/traceability-standard: k8s-pod-ownership-and-traceability-standard
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: fc-devicemgmt-operator
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: fc-devicemgmt-operator
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-operator
|
||||||
|
app.kubernetes.io/component: operator
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/port: "8080"
|
||||||
|
prometheus.io/path: "/metrics"
|
||||||
|
flowercore.io/audit-trace-id: "runtime-activity-trace"
|
||||||
|
spec:
|
||||||
|
serviceAccountName: fc-devicemgmt-operator
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 1654
|
||||||
|
fsGroupChangePolicy: OnRootMismatch
|
||||||
|
containers:
|
||||||
|
- name: operator
|
||||||
|
image: localhost/fc-devicemgmt-operator:v20260512-cx5
|
||||||
|
imagePullPolicy: Never
|
||||||
|
ports:
|
||||||
|
- name: metrics
|
||||||
|
containerPort: 8080
|
||||||
|
env:
|
||||||
|
- name: ASPNETCORE_ENVIRONMENT
|
||||||
|
value: "Production"
|
||||||
|
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
|
||||||
|
value: "false"
|
||||||
|
- name: POD_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.name
|
||||||
|
- name: POD_NAMESPACE
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.namespace
|
||||||
|
- name: FLOWERCORE_KUBERNETES_OWNER_DEPLOYMENT
|
||||||
|
value: "fc-devicemgmt-operator"
|
||||||
|
- name: FlowerCore__Service__Name
|
||||||
|
value: "FlowerCore.DeviceManagement.Operator"
|
||||||
|
- name: FlowerCore__DeviceManagement__DefaultTenantId
|
||||||
|
value: "system"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 20
|
||||||
|
periodSeconds: 30
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1654
|
||||||
|
runAsGroup: 1654
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
volumeMounts:
|
||||||
|
- name: tmp
|
||||||
|
mountPath: /tmp
|
||||||
|
- name: logs
|
||||||
|
mountPath: /app/logs
|
||||||
|
volumes:
|
||||||
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
|
- name: logs
|
||||||
|
emptyDir: {}
|
||||||
135
apps/fc-devicemgmt/deployment-web.yaml
Normal file
135
apps/fc-devicemgmt/deployment-web.yaml
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# FlowerCore.DeviceManagement Web.
|
||||||
|
#
|
||||||
|
# Source repo is expected to ship FlowerCore.DeviceManagement.Web in a later
|
||||||
|
# Sprint 9+ lane. This manifest is static-valid without requiring the image to
|
||||||
|
# exist yet; import localhost/fc-devicemgmt-web:<tag> to all schedulable RKE2
|
||||||
|
# nodes before letting ArgoCD sync a live rollout.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-web
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app: fc-devicemgmt-web
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-web
|
||||||
|
app.kubernetes.io/component: web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
annotations:
|
||||||
|
flowercore.io/traceability-standard: k8s-pod-ownership-and-traceability-standard
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: fc-devicemgmt-web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: fc-devicemgmt-web
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-web
|
||||||
|
app.kubernetes.io/component: web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/port: "8080"
|
||||||
|
prometheus.io/path: "/metrics"
|
||||||
|
flowercore.io/audit-trace-id: "runtime-activity-trace"
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 1654
|
||||||
|
fsGroupChangePolicy: OnRootMismatch
|
||||||
|
containers:
|
||||||
|
- name: web
|
||||||
|
image: localhost/fc-devicemgmt-web:v20260512-cx5
|
||||||
|
imagePullPolicy: Never
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
env:
|
||||||
|
- name: ASPNETCORE_URLS
|
||||||
|
value: "http://+:8080"
|
||||||
|
- name: ASPNETCORE_ENVIRONMENT
|
||||||
|
value: "Production"
|
||||||
|
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
|
||||||
|
value: "false"
|
||||||
|
- name: FlowerCore__Service__Name
|
||||||
|
value: "FlowerCore.DeviceManagement.Web"
|
||||||
|
- name: FlowerCore__DeviceManagement__DefaultTenantId
|
||||||
|
value: "system"
|
||||||
|
- name: FlowerCore__Database__Provider
|
||||||
|
value: "MySql"
|
||||||
|
- name: FlowerCore__Database__Host
|
||||||
|
value: "mysql.fc-mysql.svc"
|
||||||
|
- name: FlowerCore__Database__Database
|
||||||
|
value: "flowercore_devicemgmt"
|
||||||
|
- name: FlowerCore__Database__User
|
||||||
|
value: "fc_devicemgmt"
|
||||||
|
- name: FlowerCore__Database__Password
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: fc-devicemgmt-runtime
|
||||||
|
key: DB-Password
|
||||||
|
- name: FlowerCore__DeviceManagement__AgentMtls__CaPath
|
||||||
|
value: "/secrets/devicemgmt-mtls/mtls-ca.pem"
|
||||||
|
- name: FlowerCore__DeviceManagement__AgentMtls__ClientCertificatePath
|
||||||
|
value: "/secrets/devicemgmt-mtls/mtls-client.crt"
|
||||||
|
- name: FlowerCore__DeviceManagement__AgentMtls__ClientKeyPath
|
||||||
|
value: "/secrets/devicemgmt-mtls/mtls-client.key"
|
||||||
|
- name: FlowerCore__EventBus__Redis__Configuration
|
||||||
|
value: "redis.fc-redis.svc:6379"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1000m
|
||||||
|
memory: 768Mi
|
||||||
|
startupProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
failureThreshold: 30
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8080
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
failureThreshold: 3
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1654
|
||||||
|
runAsGroup: 1654
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
volumeMounts:
|
||||||
|
- name: tmp
|
||||||
|
mountPath: /tmp
|
||||||
|
- name: logs
|
||||||
|
mountPath: /app/logs
|
||||||
|
- name: devicemgmt-mtls
|
||||||
|
mountPath: /secrets/devicemgmt-mtls
|
||||||
|
readOnly: true
|
||||||
|
volumes:
|
||||||
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
|
- name: logs
|
||||||
|
emptyDir: {}
|
||||||
|
- name: devicemgmt-mtls
|
||||||
|
secret:
|
||||||
|
secretName: fc-devicemgmt-runtime
|
||||||
|
defaultMode: 0400
|
||||||
55
apps/fc-devicemgmt/ingressroute-web.yaml
Normal file
55
apps/fc-devicemgmt/ingressroute-web.yaml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# LAN ingress for FlowerCore.DeviceManagement Web.
|
||||||
|
#
|
||||||
|
# RKE2 Traefik has no built-in ACME resolver configured. Keep TLS certificate
|
||||||
|
# ownership in cert-manager Certificate/fc-devicemgmt-web-tls.
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-web
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-web
|
||||||
|
app.kubernetes.io/component: web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`devices.iamworkin.lan`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: fc-devicemgmt-web
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
secretName: fc-devicemgmt-web-tls
|
||||||
|
|
||||||
|
# Future public agent/update host gate (OFF by default):
|
||||||
|
#
|
||||||
|
# Do not enable `update.flowercore.io` here until Authentik OIDC Q-OIDC-1
|
||||||
|
# resolves the public-device-management auth model and route ownership with
|
||||||
|
# UpdateCenter. When enabled, use a separate public IngressRoute with an
|
||||||
|
# explicit Method allowlist, public-host auth middleware, and public TLS
|
||||||
|
# certificate strategy. Leaving this as comments keeps ArgoCD from stealing
|
||||||
|
# live UpdateCenter traffic.
|
||||||
|
#
|
||||||
|
# apiVersion: traefik.io/v1alpha1
|
||||||
|
# kind: IngressRoute
|
||||||
|
# metadata:
|
||||||
|
# name: fc-devicemgmt-web-public
|
||||||
|
# namespace: fc-devicemgmt
|
||||||
|
# annotations:
|
||||||
|
# flowercore.io/public-host-gate: "disabled-until-Q-OIDC-1"
|
||||||
|
# spec:
|
||||||
|
# entryPoints:
|
||||||
|
# - websecure
|
||||||
|
# routes:
|
||||||
|
# - match: Host(`update.flowercore.io`) && (Method(`GET`) || Method(`HEAD`) || Method(`POST`) || Method(`OPTIONS`))
|
||||||
|
# kind: Rule
|
||||||
|
# services:
|
||||||
|
# - name: fc-devicemgmt-web
|
||||||
|
# port: 80
|
||||||
|
# tls:
|
||||||
|
# secretName: fc-devicemgmt-public-tls
|
||||||
13
apps/fc-devicemgmt/namespace.yaml
Normal file
13
apps/fc-devicemgmt/namespace.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# FlowerCore.DeviceManagement namespace.
|
||||||
|
#
|
||||||
|
# ArgoCD discovers this directory as Application `infra-fc-devicemgmt`.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
224
apps/fc-devicemgmt/network-policy.yaml
Normal file
224
apps/fc-devicemgmt/network-policy.yaml
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
# FlowerCore.DeviceManagement NetworkPolicies.
|
||||||
|
#
|
||||||
|
# NetworkPolicies belong in bluejay-infra so ArgoCD owns rebuild state.
|
||||||
|
# Rules include Traefik post-DNAT backend ports per
|
||||||
|
# feedback_netpol_dnat_backend_port and Synology NFS egress for the requested
|
||||||
|
# cold-tier / future artifact path.
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-web-isolation
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-web
|
||||||
|
app.kubernetes.io/component: web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: fc-devicemgmt-web
|
||||||
|
policyTypes:
|
||||||
|
- Ingress
|
||||||
|
- Egress
|
||||||
|
ingress:
|
||||||
|
# LAN edge: only cluster Traefik should reach the Web pod for
|
||||||
|
# devices.iamworkin.lan.
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: traefik-system
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: traefik
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
protocol: TCP
|
||||||
|
# Direct LAN diagnostics are allowed only from FlowerCore LAN/VPN ranges.
|
||||||
|
- from:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.56.0/24
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.57.0/24
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.58.0/24
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.68.0/27
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
protocol: TCP
|
||||||
|
egress:
|
||||||
|
# CoreDNS.
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: kube-system
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
k8s-app: kube-dns
|
||||||
|
ports:
|
||||||
|
- port: 53
|
||||||
|
protocol: UDP
|
||||||
|
- port: 53
|
||||||
|
protocol: TCP
|
||||||
|
# Database namespace.
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: fc-mysql
|
||||||
|
ports:
|
||||||
|
- port: 3306
|
||||||
|
protocol: TCP
|
||||||
|
# Redis backplane for multi-replica SignalR / live-status fan-out.
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: fc-redis
|
||||||
|
ports:
|
||||||
|
- port: 6379
|
||||||
|
protocol: TCP
|
||||||
|
# Traefik VIP / in-cluster Traefik for self-callbacks and public URL
|
||||||
|
# generation tests. Include post-DNAT backend ports 8443 + 8080.
|
||||||
|
- to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.56.200/32
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: traefik-system
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: traefik
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
protocol: TCP
|
||||||
|
- port: 443
|
||||||
|
protocol: TCP
|
||||||
|
- port: 8080
|
||||||
|
protocol: TCP
|
||||||
|
- port: 8443
|
||||||
|
protocol: TCP
|
||||||
|
# Agent egress: LAN/VPN devices may run DM Agent in Generic, Kiosk, Pi,
|
||||||
|
# ThinClient, or Server mode. Keep this private-range only.
|
||||||
|
- to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.56.0/24
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.57.0/24
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.58.0/24
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.68.0/27
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
protocol: TCP
|
||||||
|
- port: 443
|
||||||
|
protocol: TCP
|
||||||
|
- port: 8080
|
||||||
|
protocol: TCP
|
||||||
|
- port: 8443
|
||||||
|
protocol: TCP
|
||||||
|
- port: 5000
|
||||||
|
protocol: TCP
|
||||||
|
- port: 5001
|
||||||
|
protocol: TCP
|
||||||
|
# Synology NFS cold-tier / artifact mount allowance.
|
||||||
|
- to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.58.3/32
|
||||||
|
ports:
|
||||||
|
- port: 2049
|
||||||
|
protocol: TCP
|
||||||
|
- port: 2049
|
||||||
|
protocol: UDP
|
||||||
|
- port: 111
|
||||||
|
protocol: TCP
|
||||||
|
- port: 111
|
||||||
|
protocol: UDP
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-operator-isolation
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-operator
|
||||||
|
app.kubernetes.io/component: operator
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: fc-devicemgmt-operator
|
||||||
|
policyTypes:
|
||||||
|
- Ingress
|
||||||
|
- Egress
|
||||||
|
ingress:
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: monitoring
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
protocol: TCP
|
||||||
|
egress:
|
||||||
|
# CoreDNS.
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: kube-system
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
k8s-app: kube-dns
|
||||||
|
ports:
|
||||||
|
- port: 53
|
||||||
|
protocol: UDP
|
||||||
|
- port: 53
|
||||||
|
protocol: TCP
|
||||||
|
# Kubernetes API for KubeOps reconciliation and Deployment UID lookup.
|
||||||
|
- to: []
|
||||||
|
ports:
|
||||||
|
- port: 443
|
||||||
|
protocol: TCP
|
||||||
|
- port: 6443
|
||||||
|
protocol: TCP
|
||||||
|
# Agent egress for operator-initiated probes / fallback command dispatch.
|
||||||
|
- to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.56.0/24
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.57.0/24
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.58.0/24
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.68.0/27
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
protocol: TCP
|
||||||
|
- port: 443
|
||||||
|
protocol: TCP
|
||||||
|
- port: 8080
|
||||||
|
protocol: TCP
|
||||||
|
- port: 8443
|
||||||
|
protocol: TCP
|
||||||
|
- port: 5000
|
||||||
|
protocol: TCP
|
||||||
|
- port: 5001
|
||||||
|
protocol: TCP
|
||||||
|
# Synology NFS allowance for future cold-tier/audit archival jobs.
|
||||||
|
- to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.58.3/32
|
||||||
|
ports:
|
||||||
|
- port: 2049
|
||||||
|
protocol: TCP
|
||||||
|
- port: 2049
|
||||||
|
protocol: UDP
|
||||||
|
- port: 111
|
||||||
|
protocol: TCP
|
||||||
|
- port: 111
|
||||||
|
protocol: UDP
|
||||||
22
apps/fc-devicemgmt/service-web.yaml
Normal file
22
apps/fc-devicemgmt/service-web.yaml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-web
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app: fc-devicemgmt-web
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-web
|
||||||
|
app.kubernetes.io/component: web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: fc-devicemgmt-web
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
protocol: TCP
|
||||||
12
apps/fc-devicemgmt/serviceaccount-operator.yaml
Normal file
12
apps/fc-devicemgmt/serviceaccount-operator.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-operator
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-operator
|
||||||
|
app.kubernetes.io/component: operator
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
@@ -1,9 +1,51 @@
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
# ci1 - Windows Server 2025 KubeVirt VM (GitHub Actions Self-Hosted Runner)
|
# ci1 — Windows Server 2025 KubeVirt VM (GitHub Actions Self-Hosted Runner)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Boots from the sysprepped containerDisk template built by the Windows VM
|
# Purpose: dedicated CI runner for FlowerCore.Updater Sandbox E2E nightly +
|
||||||
# sysprep pipeline. See docs/infrastructure/windows-vm-sysprep-pipeline.md.
|
# future fleet WPF AAT lanes. Replaces the never-registered
|
||||||
# Path A/B/C install history is preserved in git log only.
|
# `bluejay-ws-sandbox-1` runner placeholder. Andrew explicitly does NOT want
|
||||||
|
# BLUEJAY-WS registered as a runner (workstation has personal/operator state).
|
||||||
|
#
|
||||||
|
# Storage layout (2026-05-08):
|
||||||
|
# * ISO is now sourced from Synology NFS (Path B) — see
|
||||||
|
# win2025-iso-nfs-pv.yaml. The Longhorn Filesystem PVC
|
||||||
|
# `windows-server-2025-iso` below is RETAINED but UNUSED so the prior
|
||||||
|
# CDI upload state is preserved as a fallback (and so ArgoCD doesn't
|
||||||
|
# prune it on this commit). It can be deleted in a follow-up commit
|
||||||
|
# after the NFS path is proven on a successful Windows install.
|
||||||
|
#
|
||||||
|
# Status (2026-05-08): LIVE — Phase 1 prereqs satisfied:
|
||||||
|
# * Multus CNI v4.2.2 thick-plugin DaemonSet running on all 3 RKE2 nodes
|
||||||
|
# (apps/multus/multus.yaml; ApplicationSet `infra-multus` Synced/Healthy)
|
||||||
|
# * CDI v1.65.0 operator + CR Deployed (apps/cdi/; ApplicationSet
|
||||||
|
# `infra-cdi` Synced/Healthy; uploadproxy reachable via kubectl port-forward)
|
||||||
|
# * Windows Server 2025 ISO uploaded via CDI virtctl image-upload to
|
||||||
|
# PVC windows-server-2025-iso (7.7 GiB → 10Gi PVC, Bound, Upload Complete)
|
||||||
|
# * Local Administrator password generated, stored in 1Password vault
|
||||||
|
# IAmWorkin (qaphopopkryhbg353ukzhhuqoq) item id h3ix4mgfk65gmkcmvh6ly3d3hu
|
||||||
|
# * NetworkAttachmentDefinition prod-vlan57 registered (apps/kubevirt-vms/
|
||||||
|
# prod-vlan57-nad.yaml). VM still uses pod-network masquerade until Phase 1.5
|
||||||
|
# host bridge work lands (Puppet br-prod + enp86s0.57); switching is a
|
||||||
|
# one-line YAML edit + git push.
|
||||||
|
#
|
||||||
|
# See docs/infrastructure/windows-server-build-runner-plan.md "Phase 1 readiness gate".
|
||||||
|
#
|
||||||
|
# Network choice in this draft: **pod-network fallback** (Calico default).
|
||||||
|
# Outbound-only is fine for the Updater Sandbox E2E runner workload (the runner
|
||||||
|
# polls GitHub Actions over HTTPS; no inbound listener needed). Switch to a
|
||||||
|
# Multus PROD VLAN NetworkAttachmentDefinition once Multus is installed and the
|
||||||
|
# operator wants L2 access from `ci1` to other PROD VLAN services.
|
||||||
|
#
|
||||||
|
# Sizing: 8 vCPU / 16 GB RAM / 200 GB disk on Longhorn (default storageClass).
|
||||||
|
# Capacity check 2026-05-08: each RKE2 node has 16 vCPU / ~64Gi allocatable;
|
||||||
|
# 8 vCPU is ~17% of one node's allocatable, fits comfortably.
|
||||||
|
#
|
||||||
|
# Apply (after operator approval + ISO loaded):
|
||||||
|
# kubectl --kubeconfig $env:USERPROFILE\.kube\rke2.yaml apply -f apps/kubevirt-vms/ci1.yaml
|
||||||
|
#
|
||||||
|
# Connect to console for Windows install:
|
||||||
|
# virtctl --kubeconfig $env:USERPROFILE\.kube\rke2.yaml vnc ci1 -n kubevirt-vms
|
||||||
|
# (Or via Guacamole once a connection profile is added.)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
@@ -15,6 +57,248 @@ metadata:
|
|||||||
pod-security.kubernetes.io/enforce: privileged
|
pod-security.kubernetes.io/enforce: privileged
|
||||||
|
|
||||||
---
|
---
|
||||||
|
# ISO PVC — populated via CDI virtctl image-upload (CDI is now installed).
|
||||||
|
#
|
||||||
|
# **Volume mode (2026-05-08 status):** Filesystem-mode PVC. A migration to
|
||||||
|
# `volumeMode: Block` via DataVolume was attempted to address an OVMF SATA
|
||||||
|
# CDROM read timeout, but CDI v1.65.0's upload-target pod runs as uid 107
|
||||||
|
# with `capabilities.drop: [ALL]` and cannot open the underlying block
|
||||||
|
# device (`blockdev: cannot open /dev/cdi-block-volume: Permission denied`).
|
||||||
|
# Reverted to Filesystem PVC pending one of:
|
||||||
|
# - CDI deployment override granting CAP_SYS_RAWIO to upload pod
|
||||||
|
# - Pre-populated PVC via privileged init pod that dd's the ISO directly
|
||||||
|
# - Migration to a different storage class that exposes block devices
|
||||||
|
# differently (e.g. iSCSI, where Longhorn's CSI mount path may behave
|
||||||
|
# differently)
|
||||||
|
#
|
||||||
|
# Population workflow (this PVC, Filesystem mode):
|
||||||
|
# 1. virtctl --kubeconfig $env:USERPROFILE\.kube\rke2.yaml image-upload pvc \
|
||||||
|
# windows-server-2025-iso -n kubevirt-vms \
|
||||||
|
# --image-path "$env:USERPROFILE\Downloads\en-us_windows_server_2025_updated_march_2026_x64_dvd_8e06425a.iso" \
|
||||||
|
# --size 10Gi --storage-class longhorn --access-mode ReadWriteOnce \
|
||||||
|
# --uploadproxy-url https://localhost:8443 --insecure
|
||||||
|
# (--uploadproxy-url uses port-forward in practice: `kubectl port-forward
|
||||||
|
# -n cdi service/cdi-uploadproxy 8443:443 &` first.)
|
||||||
|
#
|
||||||
|
# **Open boot issue:** even with the ISO at bootOrder:1, OVMF console showed:
|
||||||
|
# BdsDxe: starting Boot0001 "UEFI QEMU DVD-ROM QM00001 " from ... Sata(...)
|
||||||
|
# BdsDxe: failed to start Boot0001 ... Time out
|
||||||
|
# Diagnosis confirmed PVC content IS a valid bootable ISO9660 image — the
|
||||||
|
# timeout is in OVMF reading from the SATA-CDROM-backed-by-filesystem-PVC.
|
||||||
|
# Block mode would likely fix it; see CDI permission issue above.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: windows-server-2025-iso
|
||||||
|
namespace: kubevirt-vms
|
||||||
|
labels:
|
||||||
|
app: ci-runner
|
||||||
|
flowercore.io/managed-by: bluejay-infra
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce # Bump to ReadOnlyMany after population for multi-VM use
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi # Server 2025 ISO is 7.7GB; 10Gi for headroom
|
||||||
|
storageClassName: longhorn
|
||||||
|
|
||||||
|
---
|
||||||
|
# Root disk PVC — empty 200Gi volume that Windows installs into.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: ci1-rootdisk
|
||||||
|
namespace: kubevirt-vms
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 200Gi
|
||||||
|
storageClassName: longhorn
|
||||||
|
|
||||||
|
---
|
||||||
|
# Sysprep ConfigMap — autounattend.xml for hands-off Windows install.
|
||||||
|
# Sets local Administrator password (REPLACE the placeholder), enables RDP,
|
||||||
|
# enables WinRM, sets hostname, and configures static-ish networking via DHCP.
|
||||||
|
# The ISO + VirtIO drivers handle the rest.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: ci1-autounattend
|
||||||
|
namespace: kubevirt-vms
|
||||||
|
data:
|
||||||
|
autounattend.xml: |
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
||||||
|
|
||||||
|
<!-- Pass 1: WindowsPE — Disk setup and VirtIO driver injection -->
|
||||||
|
<settings pass="windowsPE">
|
||||||
|
<component name="Microsoft-Windows-International-Core-WinPE"
|
||||||
|
processorArchitecture="amd64"
|
||||||
|
publicKeyToken="31bf3856ad364e35"
|
||||||
|
language="neutral" versionScope="nonSxS">
|
||||||
|
<SetupUILanguage>
|
||||||
|
<UILanguage>en-US</UILanguage>
|
||||||
|
</SetupUILanguage>
|
||||||
|
<InputLocale>en-US</InputLocale>
|
||||||
|
<SystemLocale>en-US</SystemLocale>
|
||||||
|
<UILanguage>en-US</UILanguage>
|
||||||
|
<UserLocale>en-US</UserLocale>
|
||||||
|
</component>
|
||||||
|
|
||||||
|
<component name="Microsoft-Windows-PnpCustomizationsWinPE"
|
||||||
|
processorArchitecture="amd64"
|
||||||
|
publicKeyToken="31bf3856ad364e35"
|
||||||
|
language="neutral" versionScope="nonSxS">
|
||||||
|
<DriverPaths>
|
||||||
|
<PathAndCredentials wcm:action="add" wcm:keyValue="1">
|
||||||
|
<Path>E:\amd64\2k25</Path>
|
||||||
|
</PathAndCredentials>
|
||||||
|
</DriverPaths>
|
||||||
|
</component>
|
||||||
|
|
||||||
|
<component name="Microsoft-Windows-Setup"
|
||||||
|
processorArchitecture="amd64"
|
||||||
|
publicKeyToken="31bf3856ad364e35"
|
||||||
|
language="neutral" versionScope="nonSxS">
|
||||||
|
<DiskConfiguration>
|
||||||
|
<Disk wcm:action="add">
|
||||||
|
<DiskID>0</DiskID>
|
||||||
|
<WillWipeDisk>true</WillWipeDisk>
|
||||||
|
<CreatePartitions>
|
||||||
|
<CreatePartition wcm:action="add">
|
||||||
|
<Order>1</Order>
|
||||||
|
<Size>260</Size>
|
||||||
|
<Type>EFI</Type>
|
||||||
|
</CreatePartition>
|
||||||
|
<CreatePartition wcm:action="add">
|
||||||
|
<Order>2</Order>
|
||||||
|
<Size>128</Size>
|
||||||
|
<Type>MSR</Type>
|
||||||
|
</CreatePartition>
|
||||||
|
<CreatePartition wcm:action="add">
|
||||||
|
<Order>3</Order>
|
||||||
|
<Extend>true</Extend>
|
||||||
|
<Type>Primary</Type>
|
||||||
|
</CreatePartition>
|
||||||
|
</CreatePartitions>
|
||||||
|
<ModifyPartitions>
|
||||||
|
<ModifyPartition wcm:action="add">
|
||||||
|
<Order>1</Order>
|
||||||
|
<PartitionID>1</PartitionID>
|
||||||
|
<Format>FAT32</Format>
|
||||||
|
<Label>EFI</Label>
|
||||||
|
</ModifyPartition>
|
||||||
|
<ModifyPartition wcm:action="add">
|
||||||
|
<Order>2</Order>
|
||||||
|
<PartitionID>2</PartitionID>
|
||||||
|
</ModifyPartition>
|
||||||
|
<ModifyPartition wcm:action="add">
|
||||||
|
<Order>3</Order>
|
||||||
|
<PartitionID>3</PartitionID>
|
||||||
|
<Format>NTFS</Format>
|
||||||
|
<Label>Windows</Label>
|
||||||
|
</ModifyPartition>
|
||||||
|
</ModifyPartitions>
|
||||||
|
</Disk>
|
||||||
|
</DiskConfiguration>
|
||||||
|
|
||||||
|
<ImageInstall>
|
||||||
|
<OSImage>
|
||||||
|
<InstallTo>
|
||||||
|
<DiskID>0</DiskID>
|
||||||
|
<PartitionID>3</PartitionID>
|
||||||
|
</InstallTo>
|
||||||
|
<!-- Index 2 = Standard Desktop Experience. Use 4 for Datacenter Desktop. -->
|
||||||
|
<InstallFrom>
|
||||||
|
<MetaData wcm:action="add">
|
||||||
|
<Key>/IMAGE/INDEX</Key>
|
||||||
|
<Value>2</Value>
|
||||||
|
</MetaData>
|
||||||
|
</InstallFrom>
|
||||||
|
</OSImage>
|
||||||
|
</ImageInstall>
|
||||||
|
|
||||||
|
<UserData>
|
||||||
|
<AcceptEula>true</AcceptEula>
|
||||||
|
<FullName>FlowerCore CI Runner</FullName>
|
||||||
|
<Organization>FlowerCore</Organization>
|
||||||
|
<!-- Eval install — no product key needed for 180-day evaluation -->
|
||||||
|
</UserData>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
|
||||||
|
<!-- Pass 4: Specialize — Hostname, RDP, WinRM -->
|
||||||
|
<settings pass="specialize">
|
||||||
|
<component name="Microsoft-Windows-Shell-Setup"
|
||||||
|
processorArchitecture="amd64"
|
||||||
|
publicKeyToken="31bf3856ad364e35"
|
||||||
|
language="neutral" versionScope="nonSxS">
|
||||||
|
<ComputerName>CI1</ComputerName>
|
||||||
|
<TimeZone>Central Standard Time</TimeZone>
|
||||||
|
</component>
|
||||||
|
|
||||||
|
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager"
|
||||||
|
processorArchitecture="amd64"
|
||||||
|
publicKeyToken="31bf3856ad364e35"
|
||||||
|
language="neutral" versionScope="nonSxS">
|
||||||
|
<fDenyTSConnections>false</fDenyTSConnections>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
|
||||||
|
<!-- Pass 7: OOBE — Admin account, RDP firewall, WinRM -->
|
||||||
|
<settings pass="oobeSystem">
|
||||||
|
<component name="Microsoft-Windows-Shell-Setup"
|
||||||
|
processorArchitecture="amd64"
|
||||||
|
publicKeyToken="31bf3856ad364e35"
|
||||||
|
language="neutral" versionScope="nonSxS">
|
||||||
|
<OOBE>
|
||||||
|
<HideEULAPage>true</HideEULAPage>
|
||||||
|
<HideLocalAccountScreen>true</HideLocalAccountScreen>
|
||||||
|
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
|
||||||
|
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
|
||||||
|
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
|
||||||
|
<ProtectYourPC>3</ProtectYourPC>
|
||||||
|
</OOBE>
|
||||||
|
<UserAccounts>
|
||||||
|
<AdministratorPassword>
|
||||||
|
<!-- Real password is in 1Password — vault qaphopopkryhbg353ukzhhuqoq,
|
||||||
|
item id h3ix4mgfk65gmkcmvh6ly3d3hu, title:
|
||||||
|
"ci1 Administrator (Windows Server 2025 KubeVirt VM)".
|
||||||
|
Field "autounattend AdministratorPassword Value (UTF-16-LE base64)"
|
||||||
|
matches the Value below.
|
||||||
|
To rotate: regenerate, recompute base64
|
||||||
|
$combined = $pw + "AdministratorPassword"
|
||||||
|
[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($combined))
|
||||||
|
then update both 1P item AND this Value field, recreate VM. -->
|
||||||
|
<Value>bAA3AGsANABOAHcAcgBMAG4AeQBTAHUAYgBBAHQAaQBzAFUAcAB6AEMAWQAhADkAYQBCAEEAZABtAGkAbgBpAHMAdAByAGEAdABvAHIAUABhAHMAcwB3AG8AcgBkAA==</Value>
|
||||||
|
<PlainText>false</PlainText>
|
||||||
|
</AdministratorPassword>
|
||||||
|
</UserAccounts>
|
||||||
|
<FirstLogonCommands>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>1</Order>
|
||||||
|
<CommandLine>powershell.exe -ExecutionPolicy Bypass -Command "Set-NetFirewallRule -DisplayGroup 'Remote Desktop' -Enabled True"</CommandLine>
|
||||||
|
<Description>Enable RDP firewall rule</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>2</Order>
|
||||||
|
<CommandLine>powershell.exe -ExecutionPolicy Bypass -Command "Enable-PSRemoting -Force; Set-Item WSMan:\localhost\Service\Auth\Basic $true; Set-Item WSMan:\localhost\Service\AllowUnencrypted $true"</CommandLine>
|
||||||
|
<Description>Enable WinRM (Phase 2 will pivot to HTTPS via step-ca cert)</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>3</Order>
|
||||||
|
<CommandLine>cmd.exe /c reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v EnableLUA /t REG_DWORD /d 0 /f</CommandLine>
|
||||||
|
<Description>Disable UAC (Phase 2 Puppet will re-evaluate)</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
</FirstLogonCommands>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
</unattend>
|
||||||
|
|
||||||
|
---
|
||||||
|
# VirtualMachine — Windows Server 2025 CI runner.
|
||||||
apiVersion: kubevirt.io/v1
|
apiVersion: kubevirt.io/v1
|
||||||
kind: VirtualMachine
|
kind: VirtualMachine
|
||||||
metadata:
|
metadata:
|
||||||
@@ -25,7 +309,33 @@ metadata:
|
|||||||
role: github-actions-runner
|
role: github-actions-runner
|
||||||
flowercore.io/managed-by: bluejay-infra
|
flowercore.io/managed-by: bluejay-infra
|
||||||
spec:
|
spec:
|
||||||
runStrategy: Always
|
# `running: true` is deprecated in favor of `runStrategy`. They are mutually
|
||||||
|
# exclusive — KubeVirt's validating webhook rejects any VM that sets both:
|
||||||
|
# admission webhook "virtualmachine-validator.kubevirt.io" denied the request:
|
||||||
|
# Running and RunStrategy are mutually exclusive.
|
||||||
|
# `Always` keeps a VMI running and restarts it if it crashes/exits — same
|
||||||
|
# semantics as the old `running: true`.
|
||||||
|
#
|
||||||
|
# **2026-05-08 status: VM cannot start due to a stale QEMU flock on the
|
||||||
|
# rootdisk PVC** (qemu reports `Failed to get "write" lock` on
|
||||||
|
# `/var/run/kubevirt-private/vmi-disks/rootdisk/disk.img`). The flock was
|
||||||
|
# left by a previous QEMU process during a force-deleted launcher pod
|
||||||
|
# cycle. Recovery requires either (a) a Longhorn engine restart on
|
||||||
|
# rke2-agent2, (b) a Longhorn volume detach via the longhorn-manager API
|
||||||
|
# (kubectl patch on `volume.longhorn.io/<pvc-name>` does not work — the
|
||||||
|
# spec.nodeID is reconciled back), or (c) a node reboot of rke2-agent2.
|
||||||
|
#
|
||||||
|
# **Confirmed working:** the bootOrder swap (windows-iso=1, rootdisk=2)
|
||||||
|
# and the runStrategy migration (above). The ISO PVC was successfully
|
||||||
|
# repopulated via virtctl image-upload pvc on the Filesystem-mode PVC.
|
||||||
|
#
|
||||||
|
# **Open: SATA CDROM read timeout** — even with bootOrder=1, OVMF reported
|
||||||
|
# `BdsDxe: failed to start Boot0001 ... Time out` reading the SATA CDROM
|
||||||
|
# backed by the Filesystem-mode PVC. A switch to Block-mode DataVolume
|
||||||
|
# was attempted but blocked by a CDI v1.65.0 upload-pod permission issue
|
||||||
|
# (capability drop prevents writing to the underlying block device).
|
||||||
|
# See header docstring on the ISO PVC.
|
||||||
|
runStrategy: Always # LIVE — ISO uploaded 2026-05-08, password in 1P
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
@@ -67,16 +377,73 @@ spec:
|
|||||||
firmware:
|
firmware:
|
||||||
bootloader:
|
bootloader:
|
||||||
efi:
|
efi:
|
||||||
|
# 2026-05-08: SecureBoot=false during initial install. With SecureBoot
|
||||||
|
# enabled, OVMF's BdsDxe times out reading Boot0001 from the SCSI
|
||||||
|
# CDROM ("BdsDxe: failed to start Boot0001 ... Time out") before the
|
||||||
|
# EFI bootloader signature can verify against the OVMF VARS trust DB.
|
||||||
|
# KubeVirt's `/usr/share/OVMF/OVMF_VARS.secboot.fd` template doesn't
|
||||||
|
# appear to include the Microsoft KEK/DB by default, so signed
|
||||||
|
# Windows EFI bootloaders fail validation. Disabling SecureBoot lets
|
||||||
|
# OVMF skip the chain check and boot directly. This is acceptable for
|
||||||
|
# a CI runner — TPM 2.0 is still emulated (`tpm: {}` below) so
|
||||||
|
# BitLocker / Hyper-V / WSL still work.
|
||||||
|
# When the operator wants SecureBoot back, the path is:
|
||||||
|
# 1. Custom-build OVMF_VARS.fd with Microsoft KEK/DB enrolled
|
||||||
|
# 2. Mount it into the VM via firmware.bootloader.efi.persistent
|
||||||
|
# 3. Set secureBoot: true again
|
||||||
|
# Tracked separately from the install unblock.
|
||||||
secureBoot: false
|
secureBoot: false
|
||||||
devices:
|
devices:
|
||||||
tpm: {}
|
tpm: {} # Non-persistent vTPM — sufficient for runner; no BitLocker
|
||||||
disks:
|
disks:
|
||||||
|
# bootOrder: ISO must be 1 for first-boot install (the rootdisk has no
|
||||||
|
# EFI bootloader yet). After Windows installs, it writes its own UEFI
|
||||||
|
# Boot#### entries pointing at the rootdisk's EFI partition; UEFI then
|
||||||
|
# boots from rootdisk going forward and the ISO at bootOrder:2 acts as
|
||||||
|
# a fallback for re-install scenarios.
|
||||||
|
#
|
||||||
|
# Original (broken) order had rootdisk=1, windows-iso=2 — UEFI tried
|
||||||
|
# the empty virtio disk first, got nothing, fell back to the SATA
|
||||||
|
# CDROM at Boot0001 with a short timeout, and timed out before the
|
||||||
|
# CDROM enumerated. Console showed:
|
||||||
|
# BdsDxe: failed to start Boot0001 ... Time out
|
||||||
|
# BdsDxe: No bootable option or device was found.
|
||||||
|
# Confirmed via debug pod: PVC content IS a real bootable ISO9660
|
||||||
|
# (file: "ISO 9660 CD-ROM filesystem data ... (bootable)"), so the
|
||||||
|
# only bug was boot priority.
|
||||||
|
# 2026-05-08 PM: cdrom bus SCSI + containerDisk delivery. This
|
||||||
|
# combination boots qemu cleanly and reaches OVMF, but OVMF
|
||||||
|
# BdsDxe still hits "starting Boot0001 ... Time out" on the
|
||||||
|
# cdrom — see HANDOFF.md / CODEX-STATUS.md "OPEN — ci1" for the
|
||||||
|
# full diagnostic chain. virtio-blk disk swap was attempted as a
|
||||||
|
# workaround but introduced a separate QEMU rootdisk flock issue
|
||||||
|
# without fixing the underlying OVMF cdrom problem; reverted.
|
||||||
|
# Operator decision needed for next architectural step (OVMF
|
||||||
|
# custom build with extended timeout, KubeVirt version bump,
|
||||||
|
# Hyper-V/VirtualBox-and-export, or BIOS legacy boot). The
|
||||||
|
# containerDisk distribution pipeline (build/save/scp/ctr import)
|
||||||
|
# is proven and ready to reuse for any of those.
|
||||||
|
- name: windows-iso
|
||||||
|
bootOrder: 1
|
||||||
|
cdrom:
|
||||||
|
bus: scsi
|
||||||
- name: rootdisk
|
- name: rootdisk
|
||||||
|
bootOrder: 2
|
||||||
disk:
|
disk:
|
||||||
bus: virtio
|
bus: virtio
|
||||||
|
- name: virtio-drivers
|
||||||
|
cdrom:
|
||||||
|
bus: sata
|
||||||
|
- name: sysprep
|
||||||
|
cdrom:
|
||||||
|
bus: sata
|
||||||
interfaces:
|
interfaces:
|
||||||
# Pod-network fallback for CI runner outbound traffic. Switch to
|
# Pod-network fallback for Phase 1. To switch to PROD VLAN once Multus
|
||||||
# prod-vlan57 once the bridge/NAD lane is ready for L2 access.
|
# + the prod-vlan57 NAD exist, replace this block with:
|
||||||
|
# - name: prod-net
|
||||||
|
# bridge: {}
|
||||||
|
# model: virtio
|
||||||
|
# and update the networks: stanza to use multus.networkName: kubevirt-vms/prod-vlan57
|
||||||
- name: default
|
- name: default
|
||||||
masquerade: {}
|
masquerade: {}
|
||||||
model: virtio
|
model: virtio
|
||||||
@@ -87,7 +454,55 @@ spec:
|
|||||||
pod: {}
|
pod: {}
|
||||||
volumes:
|
volumes:
|
||||||
- name: rootdisk
|
- name: rootdisk
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: ci1-rootdisk
|
||||||
|
- name: windows-iso
|
||||||
|
# 2026-05-08 PM (Path C, CONTAINERDISK): the ISO is now packaged as
|
||||||
|
# a KubeVirt containerDisk OCI image baked from
|
||||||
|
# `FROM scratch ; ADD --chown=107:107 disk.img /disk/disk.img`.
|
||||||
|
# The qemu user (uid 107) reads the ISO directly from a tmpfs view
|
||||||
|
# of the OCI layer, bypassing both:
|
||||||
|
# - Synology NFS export ACL (Path B failed: uid 107 denied at
|
||||||
|
# directory level even with mode 0777, see memory
|
||||||
|
# feedback_synology_iso_export_root_only_uid_107_denied)
|
||||||
|
# - OVMF cdrom read-window timeout (Path A and Path B's SCSI
|
||||||
|
# retry both hit `BdsDxe: failed to start Boot0001 ... Time out`
|
||||||
|
# when the cdrom was backed by a PVC the storage controller
|
||||||
|
# couldn't satisfy reads from fast enough).
|
||||||
|
#
|
||||||
|
# Image build (one-time, per ISO version):
|
||||||
|
# 1. Copy ISO to disk.img, write Dockerfile
|
||||||
|
# 2. podman build --tag localhost/win-server-2025:1.0 . (on noc1)
|
||||||
|
# 3. podman save -o win-server-2025-1.0.tar localhost/win-server-2025:1.0
|
||||||
|
# 4. SCP tar to all 3 RKE2 nodes (rke2-server, rke2-agent1, rke2-agent2)
|
||||||
|
# 5. sudo /var/lib/rancher/rke2/bin/ctr -a /run/k3s/containerd/containerd.sock \
|
||||||
|
# -n k8s.io images import /tmp/win-server-2025-1.0.tar
|
||||||
|
# Standard FC pattern per `feedback_rke2_localhost_imagepullpolicy`.
|
||||||
|
#
|
||||||
|
# When a new Windows ISO version ships, bump the tag (1.1, 1.2, ...),
|
||||||
|
# rebuild + redistribute, and update the image: line below in a new
|
||||||
|
# commit. KubeVirt picks up the new image via a VM restart.
|
||||||
|
#
|
||||||
|
# The legacy NFS PVC + PV (apps/kubevirt-vms/win2025-iso-nfs-pv.yaml)
|
||||||
|
# and CDI Longhorn PVC (`windows-server-2025-iso`) are RETAINED for
|
||||||
|
# this commit so the prior states are recoverable. Once the
|
||||||
|
# containerDisk path proves on a successful Windows install, both
|
||||||
|
# legacy artifacts can be pruned in a follow-up commit.
|
||||||
containerDisk:
|
containerDisk:
|
||||||
image: localhost/fc-win-server-2025:v1
|
image: localhost/win-server-2025:1.0
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
|
- name: virtio-drivers
|
||||||
|
containerDisk:
|
||||||
|
# Pinned to v1.8.2 (latest stable as of 2026-05-08).
|
||||||
|
# The :latest tag uses Docker manifest v1 schema which containerd
|
||||||
|
# 2.1 (RKE2 v1.34.5) refuses to pull with:
|
||||||
|
# "media type application/vnd.docker.distribution.manifest.v1+prettyjws
|
||||||
|
# is no longer supported since containerd v2.1"
|
||||||
|
# v1.8.2 is rebuilt with manifest v2/OCI and works on containerd 2.1.
|
||||||
|
# Bump available: https://quay.io/repository/kubevirt/virtio-container-disk?tab=tags
|
||||||
|
image: quay.io/kubevirt/virtio-container-disk:v1.8.2
|
||||||
|
- name: sysprep
|
||||||
|
sysprep:
|
||||||
|
configMap:
|
||||||
|
name: ci1-autounattend
|
||||||
terminationGracePeriodSeconds: 3600
|
terminationGracePeriodSeconds: 3600
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
resources:
|
|
||||||
- ci1.yaml
|
|
||||||
- prod-vlan57-nad.yaml
|
|
||||||
@@ -1,416 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Generate and optionally apply FlowerCore Authentik OIDC client assets.
|
|
||||||
|
|
||||||
Dry-run is the default. Live Authentik mutations require --apply plus an
|
|
||||||
AUTHENTIK_TOKEN bearer token and an operator-provided client secret JSON file.
|
|
||||||
The script never prints client_secret values.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import urllib.error
|
|
||||||
import urllib.parse
|
|
||||||
import urllib.request
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
|
|
||||||
CLAIMS = (
|
|
||||||
"fc:roles",
|
|
||||||
"fc:tenant",
|
|
||||||
"fc:svc",
|
|
||||||
"fc:scope",
|
|
||||||
"fc:mfa",
|
|
||||||
"flowercore_actor_id",
|
|
||||||
)
|
|
||||||
|
|
||||||
BUILTIN_SCOPE_MAPPINGS = (
|
|
||||||
"goauthentik.io/providers/oauth2/scope-openid",
|
|
||||||
"goauthentik.io/providers/oauth2/scope-profile",
|
|
||||||
"goauthentik.io/providers/oauth2/scope-email",
|
|
||||||
"goauthentik.io/providers/oauth2/scope-offline_access",
|
|
||||||
)
|
|
||||||
|
|
||||||
FLOW_CONTRACT = {
|
|
||||||
"response_type": "code",
|
|
||||||
"grant_types": ["authorization_code", "refresh_token"],
|
|
||||||
"offline_access_required": True,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class ServiceSpec:
|
|
||||||
slug: str
|
|
||||||
namespace: str
|
|
||||||
display_name: str
|
|
||||||
host: str
|
|
||||||
|
|
||||||
@property
|
|
||||||
def client_id(self) -> str:
|
|
||||||
return self.slug
|
|
||||||
|
|
||||||
@property
|
|
||||||
def provider_name(self) -> str:
|
|
||||||
return f"FlowerCore {self.display_name} OIDC"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def application_name(self) -> str:
|
|
||||||
return f"FlowerCore {self.display_name}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def issuer_url(self) -> str:
|
|
||||||
return f"https://id.iamworkin.lan/application/o/{self.slug}/"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def onepassword_item_path(self) -> str:
|
|
||||||
return f"IAmWorkin/items/{self.slug}-oidc-client"
|
|
||||||
|
|
||||||
|
|
||||||
SERVICE_SPECS = (
|
|
||||||
ServiceSpec("library", "fc-library", "Library", "library.iamworkin.lan"),
|
|
||||||
ServiceSpec("retail", "fc-retail", "Retail", "retail.iamworkin.lan"),
|
|
||||||
ServiceSpec("telephony", "telephony", "Telephony", "telephony.iamworkin.lan"),
|
|
||||||
ServiceSpec("knowledge", "knowledge", "Knowledge", "knowledge.iamworkin.lan"),
|
|
||||||
ServiceSpec("llmbridge", "fc-llm-bridge", "LlmBridge", "fc-llm-bridge.iamworkin.lan"),
|
|
||||||
ServiceSpec("mysql", "fc-mysql", "MySQL", "mysql.iamworkin.lan"),
|
|
||||||
ServiceSpec("php", "fc-php", "PHP", "php.iamworkin.lan"),
|
|
||||||
ServiceSpec("signage", "fc-signage", "Signage", "signage.iamworkin.lan"),
|
|
||||||
ServiceSpec("media", "fc-media", "Media", "media.iamworkin.lan"),
|
|
||||||
ServiceSpec("dms", "fc-dms", "DMS", "dms.iamworkin.lan"),
|
|
||||||
ServiceSpec("pimanager", "fc-pimanager", "PiManager", "pimanager.iamworkin.lan"),
|
|
||||||
ServiceSpec("distribution", "fc-distribution", "Distribution", "distribution.iamworkin.lan"),
|
|
||||||
ServiceSpec("dns", "fc-dns", "DNS", "dns.iamworkin.lan"),
|
|
||||||
ServiceSpec("print", "fc-print", "Print", "print.iamworkin.lan"),
|
|
||||||
ServiceSpec("aistation", "fc-aistation", "AiStation", "aistation.iamworkin.lan"),
|
|
||||||
ServiceSpec("irc", "irc", "IRC", "irc.iamworkin.lan"),
|
|
||||||
ServiceSpec("ttsreader", "fc-ttsreader", "TtsReader", "ttsreader.iamworkin.lan"),
|
|
||||||
ServiceSpec("chat", "fc-chat", "Chat", "chat.iamworkin.lan"),
|
|
||||||
ServiceSpec("intranet", "intranet", "Intranet", "intranet.iamworkin.lan"),
|
|
||||||
ServiceSpec("remotedesktop", "fc-desktop", "RemoteDesktop", "remotedesktop.iamworkin.lan"),
|
|
||||||
ServiceSpec("provisioning", "fc-provisioning", "Provisioning", "provisioning.iamworkin.lan"),
|
|
||||||
ServiceSpec("scoreboards", "fc-scoreboard", "Scoreboards", "scoreboards.iamworkin.lan"),
|
|
||||||
ServiceSpec("mndot", "fc-mndot", "MnDOT", "mndot.iamworkin.lan"),
|
|
||||||
ServiceSpec("kiosk", "fc-system", "Kiosk", "kiosk.iamworkin.lan"),
|
|
||||||
ServiceSpec("mike-bundle", "fc-mike-bundle", "Mike Bundle", "mike-bundle.iamworkin.lan"),
|
|
||||||
ServiceSpec("messageboard", "fc-messageboard", "MessageBoard", "messageboard.iamworkin.lan"),
|
|
||||||
ServiceSpec("menuboard", "fc-menuboard", "MenuBoard", "menuboard.iamworkin.lan"),
|
|
||||||
ServiceSpec("presentations", "fc-presentations", "Presentations", "presentations.iamworkin.lan"),
|
|
||||||
ServiceSpec("segmentdisplay", "fc-segmentdisplay", "SegmentDisplay", "segmentdisplay.iamworkin.lan"),
|
|
||||||
ServiceSpec("signalcontrol", "fc-signalcontrol", "SignalControl", "signalcontrol.iamworkin.lan"),
|
|
||||||
ServiceSpec("worldbuilder", "fc-worldbuilder", "WorldBuilder", "worldbuilder.iamworkin.lan"),
|
|
||||||
ServiceSpec("audit", "fc-audit", "Audit", "audit.iamworkin.lan"),
|
|
||||||
ServiceSpec("licensing", "fc-licensing", "Licensing", "licensing.iamworkin.lan"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def scope_mapping_payloads(service: ServiceSpec) -> list[dict[str, str]]:
|
|
||||||
managed_prefix = f"flowercore.io/authentik/oidc/{service.slug}"
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
"managed": f"{managed_prefix}/fc-roles",
|
|
||||||
"name": f"FlowerCore {service.slug} fc:roles",
|
|
||||||
"scope_name": "flowercore",
|
|
||||||
"description": "FlowerCore role claim from Authentik group memberships.",
|
|
||||||
"expression": (
|
|
||||||
"groups = [group.name for group in request.user.ak_groups.all()]\n"
|
|
||||||
"return {'fc:roles': ','.join(groups)}"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"managed": f"{managed_prefix}/fc-tenant",
|
|
||||||
"name": f"FlowerCore {service.slug} fc:tenant",
|
|
||||||
"scope_name": "flowercore",
|
|
||||||
"description": "FlowerCore tenant claim from group attribute fc_tenant_id.",
|
|
||||||
"expression": (
|
|
||||||
"for group in request.user.ak_groups.all():\n"
|
|
||||||
" tenant_id = group.attributes.get('fc_tenant_id')\n"
|
|
||||||
" if tenant_id:\n"
|
|
||||||
" return {'fc:tenant': tenant_id}\n"
|
|
||||||
"return {'fc:tenant': 'default'}"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"managed": f"{managed_prefix}/fc-svc",
|
|
||||||
"name": f"FlowerCore {service.slug} fc:svc",
|
|
||||||
"scope_name": "flowercore",
|
|
||||||
"description": "FlowerCore service slug claim.",
|
|
||||||
"expression": f"return {{'fc:svc': '{service.slug}'}}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"managed": f"{managed_prefix}/fc-scope",
|
|
||||||
"name": f"FlowerCore {service.slug} fc:scope",
|
|
||||||
"scope_name": "flowercore",
|
|
||||||
"description": "FlowerCore service permission scope claim.",
|
|
||||||
"expression": f"return {{'fc:scope': 'flowercore:{service.slug}'}}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"managed": f"{managed_prefix}/fc-mfa",
|
|
||||||
"name": f"FlowerCore {service.slug} fc:mfa",
|
|
||||||
"scope_name": "flowercore",
|
|
||||||
"description": "FlowerCore MFA satisfied session claim.",
|
|
||||||
"expression": (
|
|
||||||
"mfa_stage = request.session.get('authentik/stages/authenticator_validate')\n"
|
|
||||||
"return {'fc:mfa': bool(mfa_stage)}"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"managed": f"{managed_prefix}/flowercore-actor-id",
|
|
||||||
"name": f"FlowerCore {service.slug} flowercore_actor_id",
|
|
||||||
"scope_name": "flowercore",
|
|
||||||
"description": "FlowerCore audit actor alias for the Authentik user id.",
|
|
||||||
"expression": "return {'flowercore_actor_id': str(request.user.uid)}",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def provider_payload(
|
|
||||||
service: ServiceSpec,
|
|
||||||
args: argparse.Namespace,
|
|
||||||
mapping_ids: list[str],
|
|
||||||
client_secret: str,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
payload: dict[str, Any] = {
|
|
||||||
"name": service.provider_name,
|
|
||||||
"authorization_flow": args.authorization_flow,
|
|
||||||
"invalidation_flow": args.invalidation_flow,
|
|
||||||
"property_mappings": mapping_ids,
|
|
||||||
"client_type": "confidential",
|
|
||||||
"client_id": service.client_id,
|
|
||||||
"client_secret": client_secret,
|
|
||||||
"access_code_validity": "minutes=1",
|
|
||||||
"access_token_validity": "hours=1",
|
|
||||||
"refresh_token_validity": "days=30",
|
|
||||||
"include_claims_in_id_token": True,
|
|
||||||
"redirect_uris": [
|
|
||||||
{"matching_mode": "strict", "url": f"https://{service.host}/signin-oidc"},
|
|
||||||
{"matching_mode": "strict", "url": f"https://{service.host}/signout-callback-oidc"},
|
|
||||||
],
|
|
||||||
"sub_mode": "hashed_user_id",
|
|
||||||
"issuer_mode": "per_provider",
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.authentication_flow:
|
|
||||||
payload["authentication_flow"] = args.authentication_flow
|
|
||||||
if args.signing_key:
|
|
||||||
payload["signing_key"] = args.signing_key
|
|
||||||
|
|
||||||
return payload
|
|
||||||
|
|
||||||
|
|
||||||
def application_payload(service: ServiceSpec, provider_pk: int | str | None) -> dict[str, Any]:
|
|
||||||
return {
|
|
||||||
"name": service.application_name,
|
|
||||||
"slug": service.slug,
|
|
||||||
"provider": provider_pk,
|
|
||||||
"open_in_new_tab": True,
|
|
||||||
"meta_launch_url": f"https://{service.host}/",
|
|
||||||
"meta_description": f"FlowerCore {service.display_name} OIDC client",
|
|
||||||
"meta_publisher": "FlowerCore",
|
|
||||||
"policy_engine_mode": "all",
|
|
||||||
"group": "FlowerCore",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def load_client_secrets(path: str | None) -> dict[str, str]:
|
|
||||||
if not path:
|
|
||||||
return {}
|
|
||||||
with open(path, "r", encoding="utf-8") as handle:
|
|
||||||
data = json.load(handle)
|
|
||||||
if not isinstance(data, dict):
|
|
||||||
raise ValueError("client secret file must be a JSON object keyed by service slug")
|
|
||||||
return {str(key): str(value) for key, value in data.items()}
|
|
||||||
|
|
||||||
|
|
||||||
def redact(value: Any) -> Any:
|
|
||||||
if isinstance(value, dict):
|
|
||||||
return {
|
|
||||||
key: "<redacted>" if key == "client_secret" else redact(child)
|
|
||||||
for key, child in value.items()
|
|
||||||
}
|
|
||||||
if isinstance(value, list):
|
|
||||||
return [redact(child) for child in value]
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
class AuthentikClient:
|
|
||||||
def __init__(self, base_url: str, token: str) -> None:
|
|
||||||
self.base_url = base_url.rstrip("/")
|
|
||||||
self.token = token
|
|
||||||
|
|
||||||
def request(self, method: str, path: str, payload: dict[str, Any] | None = None) -> Any:
|
|
||||||
url = f"{self.base_url}/api/v3/{path.lstrip('/')}"
|
|
||||||
body = None
|
|
||||||
headers = {
|
|
||||||
"Accept": "application/json",
|
|
||||||
"Authorization": f"Bearer {self.token}",
|
|
||||||
}
|
|
||||||
if payload is not None:
|
|
||||||
headers["Content-Type"] = "application/json"
|
|
||||||
body = json.dumps(payload).encode("utf-8")
|
|
||||||
|
|
||||||
request = urllib.request.Request(url, data=body, headers=headers, method=method)
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(request, timeout=30) as response:
|
|
||||||
text = response.read().decode("utf-8")
|
|
||||||
except urllib.error.HTTPError as error:
|
|
||||||
error_text = error.read().decode("utf-8", errors="replace")
|
|
||||||
raise RuntimeError(f"{method} {path} failed with HTTP {error.code}: {error_text}") from error
|
|
||||||
|
|
||||||
if not text:
|
|
||||||
return None
|
|
||||||
return json.loads(text)
|
|
||||||
|
|
||||||
def first_result(self, path: str, **query: str) -> dict[str, Any] | None:
|
|
||||||
query_string = urllib.parse.urlencode(query)
|
|
||||||
response = self.request("GET", f"{path}?{query_string}")
|
|
||||||
results = response.get("results", []) if isinstance(response, dict) else []
|
|
||||||
return results[0] if results else None
|
|
||||||
|
|
||||||
|
|
||||||
def select_services(slugs: list[str]) -> list[ServiceSpec]:
|
|
||||||
if not slugs:
|
|
||||||
return list(SERVICE_SPECS)
|
|
||||||
|
|
||||||
by_slug = {service.slug: service for service in SERVICE_SPECS}
|
|
||||||
unknown = sorted(set(slugs) - set(by_slug))
|
|
||||||
if unknown:
|
|
||||||
raise ValueError(f"unknown service slug(s): {', '.join(unknown)}")
|
|
||||||
return [by_slug[slug] for slug in slugs]
|
|
||||||
|
|
||||||
|
|
||||||
def validate_specs(services: list[ServiceSpec]) -> None:
|
|
||||||
slugs = [service.slug for service in services]
|
|
||||||
if len(slugs) != len(set(slugs)):
|
|
||||||
raise ValueError("duplicate service slug in OIDC roster")
|
|
||||||
for service in services:
|
|
||||||
if not service.namespace:
|
|
||||||
raise ValueError(f"{service.slug} is missing a target namespace")
|
|
||||||
expressions = "\n".join(mapping["expression"] for mapping in scope_mapping_payloads(service))
|
|
||||||
missing_claims = [claim for claim in CLAIMS if claim not in expressions]
|
|
||||||
if missing_claims:
|
|
||||||
raise ValueError(f"{service.slug} mapping payloads miss claims: {', '.join(missing_claims)}")
|
|
||||||
|
|
||||||
|
|
||||||
def dry_run(services: list[ServiceSpec], args: argparse.Namespace) -> int:
|
|
||||||
placeholder_ids = [
|
|
||||||
*[f"<builtin-{managed.rsplit('/', 1)[-1]}-pk>" for managed in BUILTIN_SCOPE_MAPPINGS],
|
|
||||||
*[f"<{claim}-mapping-pk>" for claim in CLAIMS],
|
|
||||||
]
|
|
||||||
documents = []
|
|
||||||
for service in services:
|
|
||||||
provider = provider_payload(service, args, placeholder_ids, "<from-1password-client_secret>")
|
|
||||||
documents.append(
|
|
||||||
{
|
|
||||||
"service": service.slug,
|
|
||||||
"namespace": service.namespace,
|
|
||||||
"onepassword_item": service.onepassword_item_path,
|
|
||||||
"issuer_url": service.issuer_url,
|
|
||||||
"flow_contract": FLOW_CONTRACT,
|
|
||||||
"builtin_scope_mappings": list(BUILTIN_SCOPE_MAPPINGS),
|
|
||||||
"scope_mappings": scope_mapping_payloads(service),
|
|
||||||
"provider": redact(provider),
|
|
||||||
"application": application_payload(service, "<provider-pk>"),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if args.print_json:
|
|
||||||
print(json.dumps(documents, indent=2, sort_keys=True))
|
|
||||||
else:
|
|
||||||
print(
|
|
||||||
"Dry-run only: generated "
|
|
||||||
f"{len(services)} providers, {len(services)} applications, "
|
|
||||||
f"and {len(services) * len(CLAIMS)} scope mappings."
|
|
||||||
)
|
|
||||||
print("Use --print-json to inspect redacted payloads; use --apply for live Authentik mutation.")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def apply(services: list[ServiceSpec], args: argparse.Namespace) -> int:
|
|
||||||
token = os.environ.get("AUTHENTIK_TOKEN")
|
|
||||||
if not token:
|
|
||||||
raise ValueError("AUTHENTIK_TOKEN is required with --apply")
|
|
||||||
if not args.client_secrets_json:
|
|
||||||
raise ValueError("--client-secrets-json is required with --apply")
|
|
||||||
|
|
||||||
secrets = load_client_secrets(args.client_secrets_json)
|
|
||||||
missing = [service.slug for service in services if not secrets.get(service.slug)]
|
|
||||||
if missing:
|
|
||||||
raise ValueError(f"client secret JSON is missing slug(s): {', '.join(missing)}")
|
|
||||||
|
|
||||||
client = AuthentikClient(args.base_url, token)
|
|
||||||
for service in services:
|
|
||||||
mapping_ids: list[str] = []
|
|
||||||
for managed in BUILTIN_SCOPE_MAPPINGS:
|
|
||||||
existing = client.first_result("/propertymappings/provider/scope/", managed=managed)
|
|
||||||
if not existing:
|
|
||||||
raise ValueError(f"built-in Authentik scope mapping not found: {managed}")
|
|
||||||
mapping_ids.append(existing["pk"])
|
|
||||||
|
|
||||||
for mapping in scope_mapping_payloads(service):
|
|
||||||
existing = client.first_result("/propertymappings/provider/scope/", name=mapping["name"])
|
|
||||||
if existing and not args.update_existing:
|
|
||||||
mapping_ids.append(existing["pk"])
|
|
||||||
continue
|
|
||||||
if existing and args.update_existing:
|
|
||||||
updated = client.request("PATCH", f"/propertymappings/provider/scope/{existing['pk']}/", mapping)
|
|
||||||
mapping_ids.append(updated["pk"])
|
|
||||||
continue
|
|
||||||
created = client.request("POST", "/propertymappings/provider/scope/", mapping)
|
|
||||||
mapping_ids.append(created["pk"])
|
|
||||||
|
|
||||||
existing_provider = client.first_result("/providers/oauth2/", client_id=service.client_id)
|
|
||||||
provider_body = provider_payload(service, args, mapping_ids, secrets[service.slug])
|
|
||||||
if existing_provider and not args.update_existing:
|
|
||||||
provider_pk = existing_provider["pk"]
|
|
||||||
elif existing_provider:
|
|
||||||
updated_provider = client.request("PATCH", f"/providers/oauth2/{existing_provider['pk']}/", provider_body)
|
|
||||||
provider_pk = updated_provider["pk"]
|
|
||||||
else:
|
|
||||||
provider_pk = client.request("POST", "/providers/oauth2/", provider_body)["pk"]
|
|
||||||
|
|
||||||
app_body = application_payload(service, provider_pk)
|
|
||||||
existing_app = client.first_result("/core/applications/", slug=service.slug)
|
|
||||||
if existing_app and args.update_existing:
|
|
||||||
client.request("PATCH", f"/core/applications/{existing_app['slug']}/", app_body)
|
|
||||||
elif not existing_app:
|
|
||||||
client.request("POST", "/core/applications/", app_body)
|
|
||||||
|
|
||||||
print(f"applied {service.slug}: provider/app present, secret redacted")
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args() -> argparse.Namespace:
|
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
|
||||||
parser.add_argument("--apply", action="store_true", help="perform live Authentik REST mutations")
|
|
||||||
parser.add_argument("--update-existing", action="store_true", help="patch existing mappings/providers/applications")
|
|
||||||
parser.add_argument("--print-json", action="store_true", help="print redacted dry-run payloads")
|
|
||||||
parser.add_argument("--service", action="append", default=[], help="limit to one service slug; repeatable")
|
|
||||||
parser.add_argument("--base-url", default="http://localhost:9000", help="Authentik base URL")
|
|
||||||
parser.add_argument("--client-secrets-json", help="operator-provided JSON object of slug to client_secret")
|
|
||||||
parser.add_argument("--authorization-flow", default="<authorization-flow-uuid>")
|
|
||||||
parser.add_argument("--invalidation-flow", default="<invalidation-flow-uuid>")
|
|
||||||
parser.add_argument("--authentication-flow")
|
|
||||||
parser.add_argument("--signing-key", help="shared Authentik signing key UUID")
|
|
||||||
return parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
|
||||||
args = parse_args()
|
|
||||||
try:
|
|
||||||
services = select_services(args.service)
|
|
||||||
validate_specs(services)
|
|
||||||
if args.apply:
|
|
||||||
return apply(services, args)
|
|
||||||
return dry_run(services, args)
|
|
||||||
except Exception as error:
|
|
||||||
print(f"error: {error}", file=sys.stderr)
|
|
||||||
return 2
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raise SystemExit(main())
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
using FluentAssertions;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Xunit;
|
|
||||||
using YamlDotNet.RepresentationModel;
|
|
||||||
|
|
||||||
namespace BluejayInfraLint.Tests;
|
|
||||||
|
|
||||||
[Trait("Category", "AuthentikOidc")]
|
|
||||||
public sealed class AuthentikOidcClientRegistrationTests
|
|
||||||
{
|
|
||||||
private static readonly IReadOnlyList<ServiceClientExpectation> ExpectedClients =
|
|
||||||
[
|
|
||||||
new("library", "fc-library"),
|
|
||||||
new("retail", "fc-retail"),
|
|
||||||
new("telephony", "telephony"),
|
|
||||||
new("knowledge", "knowledge"),
|
|
||||||
new("llmbridge", "fc-llm-bridge"),
|
|
||||||
new("mysql", "fc-mysql"),
|
|
||||||
new("php", "fc-php"),
|
|
||||||
new("signage", "fc-signage"),
|
|
||||||
new("media", "fc-media"),
|
|
||||||
new("dms", "fc-dms"),
|
|
||||||
new("pimanager", "fc-pimanager"),
|
|
||||||
new("distribution", "fc-distribution"),
|
|
||||||
new("dns", "fc-dns"),
|
|
||||||
new("print", "fc-print"),
|
|
||||||
new("aistation", "fc-aistation"),
|
|
||||||
new("irc", "irc"),
|
|
||||||
new("ttsreader", "fc-ttsreader"),
|
|
||||||
new("chat", "fc-chat"),
|
|
||||||
new("intranet", "intranet"),
|
|
||||||
new("remotedesktop", "fc-desktop"),
|
|
||||||
new("provisioning", "fc-provisioning"),
|
|
||||||
new("scoreboards", "fc-scoreboard"),
|
|
||||||
new("mndot", "fc-mndot"),
|
|
||||||
new("kiosk", "fc-system"),
|
|
||||||
new("mike-bundle", "fc-mike-bundle"),
|
|
||||||
new("messageboard", "fc-messageboard"),
|
|
||||||
new("menuboard", "fc-menuboard"),
|
|
||||||
new("presentations", "fc-presentations"),
|
|
||||||
new("segmentdisplay", "fc-segmentdisplay"),
|
|
||||||
new("signalcontrol", "fc-signalcontrol"),
|
|
||||||
new("worldbuilder", "fc-worldbuilder"),
|
|
||||||
new("audit", "fc-audit"),
|
|
||||||
new("licensing", "fc-licensing"),
|
|
||||||
];
|
|
||||||
|
|
||||||
public static TheoryData<string, string> ExpectedClientRows()
|
|
||||||
{
|
|
||||||
var data = new TheoryData<string, string>();
|
|
||||||
foreach (var client in ExpectedClients)
|
|
||||||
{
|
|
||||||
data.Add(client.Slug, client.Namespace);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(ExpectedClientRows))]
|
|
||||||
public void OidcClientManifest_MatchesOnePasswordOperatorContract(string slug, string targetNamespace)
|
|
||||||
{
|
|
||||||
var manifest = LoadClientManifest(slug);
|
|
||||||
manifest.Scalar("apiVersion").Should().Be("onepassword.com/v1");
|
|
||||||
manifest.Scalar("kind").Should().Be("OnePasswordItem");
|
|
||||||
manifest.Scalar("metadata", "name").Should().Be($"{slug}-oidc-client");
|
|
||||||
manifest.Scalar("metadata", "namespace").Should().Be(targetNamespace);
|
|
||||||
manifest.Scalar("metadata", "labels", "app.kubernetes.io/component")
|
|
||||||
.Should().Be("authentik-oidc-client");
|
|
||||||
manifest.Scalar("metadata", "labels", "flowercore.io/authentik-client-slug")
|
|
||||||
.Should().Be(slug);
|
|
||||||
manifest.Scalar("metadata", "annotations", "flowercore.io/expected-fields")
|
|
||||||
.Should().Be("client_id,client_secret,issuer_url");
|
|
||||||
manifest.Scalar("spec", "itemPath")
|
|
||||||
.Should().Be($"vaults/IAmWorkin/items/{slug}-oidc-client");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void AuthentikKustomization_ReferencesEveryClientManifest()
|
|
||||||
{
|
|
||||||
var kustomizationPath = Path.Combine(BluejayRoot(), "apps", "authentik", "kustomization.yaml");
|
|
||||||
var text = File.ReadAllText(kustomizationPath);
|
|
||||||
|
|
||||||
foreach (var client in ExpectedClients)
|
|
||||||
{
|
|
||||||
text.Should().Contain($"clients/{client.Slug}-oidc-client.yaml");
|
|
||||||
}
|
|
||||||
|
|
||||||
Regex.Matches(text, @"clients/[-a-z0-9]+-oidc-client\.yaml")
|
|
||||||
.Select(match => match.Value)
|
|
||||||
.Distinct(StringComparer.Ordinal)
|
|
||||||
.Should()
|
|
||||||
.HaveCount(ExpectedClients.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BulkClientScript_HasDryRunDefaultAndRequiredClaimPayloads()
|
|
||||||
{
|
|
||||||
var scriptPath = Path.Combine(BluejayRoot(), "scripts", "authentik-bulk-client-create.py");
|
|
||||||
var script = File.ReadAllText(scriptPath);
|
|
||||||
|
|
||||||
script.Should().Contain("--apply");
|
|
||||||
script.Should().Contain("Dry-run only");
|
|
||||||
script.Should().Contain("AUTHENTIK_TOKEN");
|
|
||||||
script.Should().Contain("client_secrets_json");
|
|
||||||
script.Should().Contain("scope-offline_access");
|
|
||||||
script.Should().Contain("authorization_code");
|
|
||||||
script.Should().Contain("refresh_token");
|
|
||||||
|
|
||||||
foreach (var claim in new[] { "fc:roles", "fc:tenant", "fc:svc", "fc:scope", "fc:mfa", "flowercore_actor_id" })
|
|
||||||
{
|
|
||||||
script.Should().Contain(claim);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BulkClientScript_DryRunGeneratesAllServicesWithoutSecrets()
|
|
||||||
{
|
|
||||||
var scriptPath = Path.Combine(BluejayRoot(), "scripts", "authentik-bulk-client-create.py");
|
|
||||||
var startInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = "python",
|
|
||||||
WorkingDirectory = BluejayRoot(),
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
};
|
|
||||||
startInfo.ArgumentList.Add(scriptPath);
|
|
||||||
startInfo.ArgumentList.Add("--print-json");
|
|
||||||
|
|
||||||
using var process = Process.Start(startInfo)
|
|
||||||
?? throw new InvalidOperationException("Could not start python dry-run.");
|
|
||||||
var stdout = process.StandardOutput.ReadToEnd();
|
|
||||||
var stderr = process.StandardError.ReadToEnd();
|
|
||||||
process.WaitForExit(15000).Should().BeTrue(stderr);
|
|
||||||
process.ExitCode.Should().Be(0, stderr);
|
|
||||||
|
|
||||||
foreach (var client in ExpectedClients)
|
|
||||||
{
|
|
||||||
stdout.Should().Contain($"\"service\": \"{client.Slug}\"");
|
|
||||||
stdout.Should().Contain($"\"namespace\": \"{client.Namespace}\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout.Should().Contain("\"client_secret\": \"<redacted>\"");
|
|
||||||
stdout.Should().NotMatchRegex("\"client_secret\"\\s*:\\s*\"(?!<redacted>)[^\"]+\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ClientManifests_DoNotContainInlineSecretMaterial()
|
|
||||||
{
|
|
||||||
foreach (var client in ExpectedClients)
|
|
||||||
{
|
|
||||||
var path = ClientManifestPath(client.Slug);
|
|
||||||
var text = File.ReadAllText(path);
|
|
||||||
|
|
||||||
text.Should().NotContain("client_secret:");
|
|
||||||
text.Should().NotContain("password:");
|
|
||||||
text.Should().NotContain("secret:");
|
|
||||||
text.Should().Contain($"IAmWorkin/items/{client.Slug}-oidc-client");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static YamlMappingNode LoadClientManifest(string slug)
|
|
||||||
{
|
|
||||||
using var reader = File.OpenText(ClientManifestPath(slug));
|
|
||||||
var stream = new YamlStream();
|
|
||||||
stream.Load(reader);
|
|
||||||
return stream.Documents[0].RootNode.Should().BeOfType<YamlMappingNode>().Subject;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ClientManifestPath(string slug) =>
|
|
||||||
Path.Combine(BluejayRoot(), "apps", "authentik", "clients", $"{slug}-oidc-client.yaml");
|
|
||||||
|
|
||||||
private static string BluejayRoot()
|
|
||||||
{
|
|
||||||
var current = new DirectoryInfo(AppContext.BaseDirectory);
|
|
||||||
while (current is not null)
|
|
||||||
{
|
|
||||||
if (Directory.Exists(Path.Combine(current.FullName, "apps"))
|
|
||||||
&& File.Exists(Path.Combine(current.FullName, "README.md")))
|
|
||||||
{
|
|
||||||
return current.FullName;
|
|
||||||
}
|
|
||||||
|
|
||||||
current = current.Parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new DirectoryNotFoundException("Could not find bluejay-infra root.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed record ServiceClientExpectation(string Slug, string Namespace);
|
|
||||||
}
|
|
||||||
@@ -291,6 +291,184 @@ public sealed class FleetManifestLintTests
|
|||||||
violations.Should().BeEmpty();
|
violations.Should().BeEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FcDeviceManagement_MustShipExpectedManifestSet()
|
||||||
|
{
|
||||||
|
var appRoot = Path.Combine(Inventory.BluejayRoot, "apps", "fc-devicemgmt");
|
||||||
|
Directory.Exists(appRoot).Should().BeTrue("Sprint 8 Cx-5 owns apps/fc-devicemgmt.");
|
||||||
|
|
||||||
|
var expectedFiles = new[]
|
||||||
|
{
|
||||||
|
"1password-item.yaml",
|
||||||
|
"argocd-application.yaml",
|
||||||
|
"certificate-web.yaml",
|
||||||
|
"clusterrole-operator.yaml",
|
||||||
|
"clusterrolebinding-operator.yaml",
|
||||||
|
"deployment-operator.yaml",
|
||||||
|
"deployment-web.yaml",
|
||||||
|
"ingressroute-web.yaml",
|
||||||
|
"namespace.yaml",
|
||||||
|
"network-policy.yaml",
|
||||||
|
"service-web.yaml",
|
||||||
|
"serviceaccount-operator.yaml",
|
||||||
|
};
|
||||||
|
|
||||||
|
Directory.GetFiles(appRoot, "*.yaml")
|
||||||
|
.Select(Path.GetFileName)
|
||||||
|
.Should()
|
||||||
|
.BeEquivalentTo(expectedFiles);
|
||||||
|
|
||||||
|
foreach (var expectedFile in expectedFiles)
|
||||||
|
{
|
||||||
|
FcDeviceManagementDocuments()
|
||||||
|
.Should()
|
||||||
|
.Contain(document => document.RelativePath == $"fc-devicemgmt/{expectedFile}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FcDeviceManagement_ObjectsMustCarryStandardTraceabilityLabels()
|
||||||
|
{
|
||||||
|
var requiredLabels = new[]
|
||||||
|
{
|
||||||
|
"app.kubernetes.io/name",
|
||||||
|
"app.kubernetes.io/part-of",
|
||||||
|
"app.kubernetes.io/managed-by",
|
||||||
|
"flowercore.io/tenant-id",
|
||||||
|
"flowercore.io/created-by",
|
||||||
|
};
|
||||||
|
|
||||||
|
var violations = FcDeviceManagementDocuments()
|
||||||
|
.SelectMany(document => requiredLabels
|
||||||
|
.Where(label => string.IsNullOrWhiteSpace(document.Scalar("metadata", "labels", label)))
|
||||||
|
.Select(label => $"{document.Descriptor} is missing metadata.labels['{label}']."))
|
||||||
|
.Concat(FcDeviceManagementDocuments()
|
||||||
|
.Where(document => document.Kind == "Deployment")
|
||||||
|
.SelectMany(document => requiredLabels
|
||||||
|
.Where(label => string.IsNullOrWhiteSpace(document.Scalar("spec", "template", "metadata", "labels", label)))
|
||||||
|
.Select(label => $"{document.Descriptor} pod template is missing metadata.labels['{label}'].")))
|
||||||
|
.Concat(FcDeviceManagementDocuments()
|
||||||
|
.Where(document => document.Kind == "Deployment")
|
||||||
|
.Where(document => string.IsNullOrWhiteSpace(document.Scalar("spec", "template", "metadata", "annotations", "flowercore.io/audit-trace-id")))
|
||||||
|
.Select(document => $"{document.Descriptor} pod template is missing flowercore.io/audit-trace-id."))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
violations.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FcDeviceManagement_IngressMustUseCertManagerAndKeepPublicHostDisabled()
|
||||||
|
{
|
||||||
|
var appText = string.Join(
|
||||||
|
Environment.NewLine,
|
||||||
|
Directory.GetFiles(Path.Combine(Inventory.BluejayRoot, "apps", "fc-devicemgmt"), "*.yaml")
|
||||||
|
.Select(File.ReadAllText));
|
||||||
|
|
||||||
|
appText.Should().NotContain("certResolver");
|
||||||
|
appText.Should().Contain("update.flowercore.io");
|
||||||
|
appText.Should().Contain("disabled-until-Q-OIDC-1");
|
||||||
|
|
||||||
|
FcDeviceManagementDocuments()
|
||||||
|
.Where(document => document.Kind == "IngressRoute")
|
||||||
|
.SelectMany(document => document.MappingSequence("spec", "routes"))
|
||||||
|
.Select(route => ManifestNodeExtensions.Scalar(route, "match") ?? string.Empty)
|
||||||
|
.Should()
|
||||||
|
.Contain(match => match.Contains("Host(`devices.iamworkin.lan`)", StringComparison.Ordinal))
|
||||||
|
.And.NotContain(match => match.Contains("Host(`update.flowercore.io`)", StringComparison.Ordinal));
|
||||||
|
|
||||||
|
var certificate = FcDeviceManagementDocuments()
|
||||||
|
.Single(document => document.Kind == "Certificate" && document.Name == "fc-devicemgmt-web-tls");
|
||||||
|
|
||||||
|
certificate.Scalar("spec", "issuerRef", "name").Should().Be("step-ca-acme");
|
||||||
|
certificate.Scalar("spec", "issuerRef", "kind").Should().Be("ClusterIssuer");
|
||||||
|
ManifestNodeExtensions.ScalarSequence(certificate.Root, "spec", "dnsNames")
|
||||||
|
.Should()
|
||||||
|
.ContainSingle("devices.iamworkin.lan");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FcDeviceManagement_OperatorRbacMustCoverDevicesAndOwnerLookup()
|
||||||
|
{
|
||||||
|
var clusterRole = FcDeviceManagementDocuments()
|
||||||
|
.Single(document => document.Kind == "ClusterRole" && document.Name == "fc-devicemgmt-operator");
|
||||||
|
var allScalars = clusterRole.AllScalars().ToList();
|
||||||
|
|
||||||
|
allScalars.Should().Contain("devices.flowercore.io");
|
||||||
|
allScalars.Should().Contain("*");
|
||||||
|
allScalars.Should().Contain("deployments");
|
||||||
|
allScalars.Should().Contain("get");
|
||||||
|
|
||||||
|
var operatorDeployment = FcDeviceManagementDocuments()
|
||||||
|
.Single(document => document.Kind == "Deployment" && document.Name == "fc-devicemgmt-operator");
|
||||||
|
|
||||||
|
operatorDeployment.AllScalars().Should().Contain("FLOWERCORE_KUBERNETES_OWNER_DEPLOYMENT");
|
||||||
|
operatorDeployment.AllScalars().Should().Contain("fc-devicemgmt-operator");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FcDeviceManagement_RuntimeSecretsMustUseOnePasswordItemPattern()
|
||||||
|
{
|
||||||
|
var item = FcDeviceManagementDocuments()
|
||||||
|
.Single(document => document.Kind == "OnePasswordItem" && document.Name == "fc-devicemgmt-runtime");
|
||||||
|
|
||||||
|
item.Scalar("spec", "itemPath")
|
||||||
|
.Should()
|
||||||
|
.Be("vaults/IAmWorkin/items/FlowerCore DeviceManagement Runtime");
|
||||||
|
|
||||||
|
var appText = string.Join(
|
||||||
|
Environment.NewLine,
|
||||||
|
Directory.GetFiles(Path.Combine(Inventory.BluejayRoot, "apps", "fc-devicemgmt"), "*.yaml")
|
||||||
|
.Select(File.ReadAllText));
|
||||||
|
|
||||||
|
FcDeviceManagementDocuments().Should().NotContain(document => document.Kind == "Secret");
|
||||||
|
appText.Should().Contain("secretKeyRef:");
|
||||||
|
appText.Should().Contain("secretName: fc-devicemgmt-runtime");
|
||||||
|
appText.Should().NotContain("stringData:");
|
||||||
|
appText.Should().NotContain("from-literal");
|
||||||
|
appText.Should().NotContain("tls.key:");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FcDeviceManagement_NetworkPoliciesMustAllowLanAgentsSynologyAndDnatPorts()
|
||||||
|
{
|
||||||
|
var policies = FcDeviceManagementDocuments()
|
||||||
|
.Where(document => document.Kind == "NetworkPolicy")
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
policies.Should().HaveCount(2);
|
||||||
|
|
||||||
|
var combinedScalars = policies.SelectMany(policy => policy.AllScalars()).ToList();
|
||||||
|
combinedScalars.Should().Contain("10.0.56.0/24");
|
||||||
|
combinedScalars.Should().Contain("10.0.57.0/24");
|
||||||
|
combinedScalars.Should().Contain("10.0.58.0/24");
|
||||||
|
combinedScalars.Should().Contain("10.0.68.0/27");
|
||||||
|
combinedScalars.Should().Contain("10.0.58.3/32");
|
||||||
|
|
||||||
|
var combinedEgressPorts = policies.SelectMany(policy => policy.EgressPorts()).ToHashSet(StringComparer.Ordinal);
|
||||||
|
combinedEgressPorts.Should().Contain(new[] { "80", "443", "8080", "8443", "2049", "111" });
|
||||||
|
|
||||||
|
var traefikVipPolicies = policies
|
||||||
|
.Where(policy => policy.AllScalars().Any(value => value.Contains("10.0.56.200", StringComparison.Ordinal)))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
traefikVipPolicies.Should().ContainSingle();
|
||||||
|
traefikVipPolicies[0].EgressPorts().Should().Contain(new[] { "80", "443", "8080", "8443" });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FcDeviceManagement_ArgocdApplicationMustMatchApplicationSetDiscoveryConventions()
|
||||||
|
{
|
||||||
|
var application = FcDeviceManagementDocuments()
|
||||||
|
.Single(document => document.Kind == "Application" && document.Name == "infra-fc-devicemgmt");
|
||||||
|
|
||||||
|
application.Namespace.Should().Be("argocd");
|
||||||
|
application.Scalar("spec", "source", "repoURL")
|
||||||
|
.Should()
|
||||||
|
.Be("http://gitea-clusterip.gitea.svc.cluster.local:3000/bluejay/bluejay-infra.git");
|
||||||
|
application.Scalar("spec", "source", "path").Should().Be("apps/fc-devicemgmt");
|
||||||
|
application.Scalar("spec", "destination", "namespace").Should().Be("fc-devicemgmt");
|
||||||
|
}
|
||||||
|
|
||||||
private static IEnumerable<string> ProbeViolations(
|
private static IEnumerable<string> ProbeViolations(
|
||||||
ManifestDocument document,
|
ManifestDocument document,
|
||||||
YamlMappingNode container,
|
YamlMappingNode container,
|
||||||
@@ -314,6 +492,13 @@ public sealed class FleetManifestLintTests
|
|||||||
$"{document.Descriptor} container '{containerName}' still uses {probeKey}.httpGet on /health.",
|
$"{document.Descriptor} container '{containerName}' still uses {probeKey}.httpGet on /health.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<ManifestDocument> FcDeviceManagementDocuments()
|
||||||
|
{
|
||||||
|
return Inventory.Documents
|
||||||
|
.Where(document => document.RelativePath.StartsWith("fc-devicemgmt/", StringComparison.Ordinal))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class ManifestInventory
|
internal sealed class ManifestInventory
|
||||||
|
|||||||
Reference in New Issue
Block a user