deploy(updater): gate public UpdateCenter host
This commit is contained in:
@@ -101,7 +101,7 @@ curl -sk -X DELETE https://dns.iamworkin.lan/api/v1/servers/<serverId>/zones/iam
|
|||||||
- **StatefulSet PVC drift**: `volumeClaimTemplates` needs explicit `volumeMode: Filesystem` or ArgoCD SSA self-heals forever. See memory `feedback_argocd_statefulset_pvc_drift.md`.
|
- **StatefulSet PVC drift**: `volumeClaimTemplates` needs explicit `volumeMode: Filesystem` or ArgoCD SSA self-heals forever. See memory `feedback_argocd_statefulset_pvc_drift.md`.
|
||||||
- **IngressRoute namespace split**: this RKE2 Traefik install does not allow cross-namespace service refs. Keep the `IngressRoute`, backend `Service`, and TLS secret in the same namespace; if one host is shared across namespaces, duplicate the `Certificate` and move the route next to the destination service.
|
- **IngressRoute namespace split**: this RKE2 Traefik install does not allow cross-namespace service refs. Keep the `IngressRoute`, backend `Service`, and TLS secret in the same namespace; if one host is shared across namespaces, duplicate the `Certificate` and move the route next to the destination service.
|
||||||
- **Public read-only hosts**: if a public host fronts a service that also exposes admin writes internally, add a Traefik route match like `Host(...) && (Method(GET) || Method(HEAD))` on the public edge instead of trusting the app to reject unsafe methods.
|
- **Public read-only hosts**: if a public host fronts a service that also exposes admin writes internally, add a Traefik route match like `Host(...) && (Method(GET) || Method(HEAD))` on the public edge instead of trusting the app to reject unsafe methods.
|
||||||
- **Public read-write allowlist hosts**: if a public host accepts a tightly bounded write surface (e.g. bootstrap-JWT POST), pin the allowlist as `(Method(GET) || Method(HEAD) || Method(POST) || Method(OPTIONS))`. PUT/PATCH/DELETE must still 404 at the route. Track A's `updatecenter.iamworkin.lan` / `updates.iamworkin.lan` are the canonical example. The lint test enforces this invariant.
|
- **Public read-write allowlist hosts**: if a public host accepts a tightly bounded write surface (e.g. bootstrap-JWT POST), pin the allowlist as `(Method(GET) || Method(HEAD) || Method(POST) || Method(OPTIONS))`. PUT/PATCH/DELETE must still 404 at the route. Internal UpdateCenter hosts (`updatecenter.iamworkin.lan` / `updates.iamworkin.lan`) are the canonical example. Public UpdateCenter delivery hosts (`update.flowercore.io` / `updates.flowercore.io`) stay GET/HEAD-only and share-link gated until an explicit operator decision changes that posture.
|
||||||
- **Traefik VIP netpols**: when a `NetworkPolicy` allows `10.0.56.200`, also allow the post-DNAT backend ports (`8443` for TLS plus `8080` or `8000` for HTTP) or Calico will drop the rewritten flow.
|
- **Traefik VIP netpols**: when a `NetworkPolicy` allows `10.0.56.200`, also allow the post-DNAT backend ports (`8443` for TLS plus `8080` or `8000` for HTTP) or Calico will drop the rewritten flow.
|
||||||
- **Auth-safe probes**: services behind API-key or global auth middleware should prefer `tcpSocket` probes unless `/health` is explicitly exempted before the middleware runs.
|
- **Auth-safe probes**: services behind API-key or global auth middleware should prefer `tcpSocket` probes unless `/health` is explicitly exempted before the middleware runs.
|
||||||
- **ArgoCD must use internal Gitea URL**: `http://gitea-clusterip.gitea.svc.cluster.local:3000/bluejay/bluejay-infra.git`, not the external HTTPS URL (step-ca cert isn't trusted by ArgoCD). The `ApplicationSet` and any hand-created `Application` must both use the internal URL.
|
- **ArgoCD must use internal Gitea URL**: `http://gitea-clusterip.gitea.svc.cluster.local:3000/bluejay/bluejay-infra.git`, not the external HTTPS URL (step-ca cert isn't trusted by ArgoCD). The `ApplicationSet` and any hand-created `Application` must both use the internal URL.
|
||||||
|
|||||||
@@ -53,13 +53,17 @@
|
|||||||
"name": "FlowerCore__Updater__BundleStorage__LocalFs__RootDirectory",
|
"name": "FlowerCore__Updater__BundleStorage__LocalFs__RootDirectory",
|
||||||
"value": "/data/bundles"
|
"value": "/data/bundles"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "FlowerCore__Updater__PublicShares__RequirePublicVisibilityOnPublicHosts",
|
"name": "FlowerCore__Updater__PublicShares__RequirePublicVisibilityOnPublicHosts",
|
||||||
"value": "true"
|
"value": "true"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "FlowerCore__Updater__PublicShares__Links__0__Code",
|
"name": "FlowerCore__Updater__PublicShares__RequireShareLinkOnPublicHosts",
|
||||||
"value": "8f3c2a9e7d41"
|
"value": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FlowerCore__Updater__PublicShares__Links__0__Code",
|
||||||
|
"value": "8f3c2a9e7d41"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "FlowerCore__Updater__PublicShares__Links__0__AppId",
|
"name": "FlowerCore__Updater__PublicShares__Links__0__AppId",
|
||||||
@@ -195,7 +199,7 @@
|
|||||||
"value": "26843545600"
|
"value": "26843545600"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image": "localhost/fc-updater-web:v20260617-sec5-913c6a9",
|
"image": "localhost/fc-updater-web:v20260618-public-exposure-6c0d0e4",
|
||||||
"imagePullPolicy": "Never",
|
"imagePullPolicy": "Never",
|
||||||
"securityContext": {
|
"securityContext": {
|
||||||
"allowPrivilegeEscalation": false,
|
"allowPrivilegeEscalation": false,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
"kind": "Rule",
|
"kind": "Rule",
|
||||||
"match": "(Host(`update.flowercore.io`) || Host(`updates.flowercore.io`)) && (Method(`GET`) || Method(`HEAD`) || Method(`POST`) || Method(`OPTIONS`))",
|
"match": "(Host(`update.flowercore.io`) || Host(`updates.flowercore.io`)) && (Method(`GET`) || Method(`HEAD`))",
|
||||||
"priority": 100,
|
"priority": 100,
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ public sealed class FleetManifestLintTests
|
|||||||
{
|
{
|
||||||
"brochure.flowercore.io",
|
"brochure.flowercore.io",
|
||||||
"dist.flowercore.io",
|
"dist.flowercore.io",
|
||||||
|
"update.flowercore.io",
|
||||||
|
"updates.flowercore.io",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hosts that allow a tightly bounded write surface in addition to GET/HEAD.
|
// Hosts that allow a tightly bounded write surface in addition to GET/HEAD.
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
package bluejayinfra.public_method_allowlist
|
package bluejayinfra.public_method_allowlist
|
||||||
|
|
||||||
public_hosts := {"brochure.flowercore.io", "dist.flowercore.io", "dns.iamworkin.lan"}
|
public_hosts := {
|
||||||
|
"brochure.flowercore.io",
|
||||||
|
"dist.flowercore.io",
|
||||||
|
"dns.iamworkin.lan",
|
||||||
|
"update.flowercore.io",
|
||||||
|
"updates.flowercore.io",
|
||||||
|
}
|
||||||
|
|
||||||
deny[msg] {
|
deny[msg] {
|
||||||
input.kind == "IngressRoute"
|
input.kind == "IngressRoute"
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ package bluejayinfra.public_readwrite_allowlist
|
|||||||
public_readwrite_hosts := {
|
public_readwrite_hosts := {
|
||||||
"updatecenter.iamworkin.lan",
|
"updatecenter.iamworkin.lan",
|
||||||
"updates.iamworkin.lan",
|
"updates.iamworkin.lan",
|
||||||
"update.flowercore.io",
|
|
||||||
"updates.flowercore.io",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required_methods := {"GET", "HEAD", "POST", "OPTIONS"}
|
required_methods := {"GET", "HEAD", "POST", "OPTIONS"}
|
||||||
|
|||||||
Reference in New Issue
Block a user