feat(push): make gateway origin configurable (#7261)

Make the push gateway origin configurable across the relay, gateway,
mobile client, and deployment chart.

This is a fresh deployment. The gateway has only run on personal
development infrastructure, and push has no existing enabled users.
Legacy migration, configured-to-unconfigured mobile transitions, and
rolling upgrades from pre-launch gateway binaries are not supported.

Populated pre-launch databases are rejected with an informative error
before migrations run; their data is untouched. Mobile builds may omit
the gateway URL, leaving push unavailable. Once push is enabled,
subsequent builds must retain that configuration.

### Validation

- All applicable CI checks pass at
`5f546cfaf403f0386f146d213adfb4392f4ec29c`, including Rust,
mobile/Swift, desktop/integration, security, image builds, and Helm
checks. An unrelated agent cancellation test passed on retry.
- PostgreSQL CI passed 384 tests, including the production-entry-point
regression for refusing populated pre-launch databases before
migrations, fresh initialization, and subsequent initialized
deployments.
- Focused local validation passed all 10 gateway PostgreSQL tests under
the CI discovery filter and all 3 unconfigured-mobile tests.
Repository-wide PostgreSQL discovery validation and the complete
pre-push hooks passed.
- Physical-device APNs/App Attest behavior and deployment to a live
cluster were not verified in this work.

---------

Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Tom Brow
2026-09-15 14:56:45 -07:00
committed by GitHub
parent 41c5ace937
commit 76f7e5ef0c
53 changed files with 1941 additions and 492 deletions

View File

@@ -0,0 +1,2 @@
# Chart-testing input only. Real deployments must inject their own origin.
gatewayOrigin: https://push.example

View File

@@ -32,7 +32,7 @@ spec:
env:
- { name: BUZZ_PUSH_BIND_ADDR, value: "0.0.0.0:8080" }
- { name: BUZZ_PUSH_HEALTH_ADDR, value: "0.0.0.0:8081" }
- { name: BUZZ_PUSH_PUBLIC_DELIVERY_URL, value: {{ .Values.publicDeliveryUrl | quote }} }
- { name: BUZZ_PUSH_GATEWAY_ORIGIN, value: {{ required "gatewayOrigin is required" .Values.gatewayOrigin | quote }} }
- { name: BUZZ_PUSH_MAX_GRANT_LIFETIME_SECONDS, value: {{ .Values.maxGrantLifetimeSeconds | quote }} }
- { name: BUZZ_PUSH_APP_ATTEST_ROOT_CERT_PATH, value: /run/buzz/app-attest/root.pem }
- { name: BUZZ_PUSH_DOGFOOD_APP_ATTEST_APP_ID, value: {{ .Values.profiles.dogfood.appAttestAppId | quote }} }

View File

@@ -1,11 +1,13 @@
{{- if .Values.httpRoute.enabled }}
{{- $gatewayURL := urlParse (required "gatewayOrigin is required" .Values.gatewayOrigin) }}
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: {{ include "push.name" . }}
spec:
parentRefs: {{- toYaml .Values.httpRoute.parentRefs | nindent 4 }}
hostnames: {{- toYaml .Values.httpRoute.hostnames | nindent 4 }}
hostnames:
- {{ required "gatewayOrigin must include a hostname" $gatewayURL.host | quote }}
rules:
- matches:
- path: { type: PathPrefix, value: / }

View File

@@ -2,15 +2,17 @@
set -euo pipefail
out=$(mktemp); production_out=$(mktemp); route_out=$(mktemp); datadog_out=$(mktemp)
trap 'rm -f "$out" "$production_out" "$route_out" "$datadog_out" "${monitoring_out:-}"' EXIT
gateway_origin_arg=(--set 'gatewayOrigin=https://push.example')
# Defaults must lint and render without parameter injection.
helm lint deploy/charts/buzz-push-gateway >/dev/null
helm template push deploy/charts/buzz-push-gateway >"$out"
# Generic values require the deployment-owned gateway origin.
helm lint deploy/charts/buzz-push-gateway "${gateway_origin_arg[@]}" >/dev/null
helm template push deploy/charts/buzz-push-gateway "${gateway_origin_arg[@]}" >"$out"
# Production values support a platform-owned ingress without rendering an
# HTTPRoute. The environment-owned inputs remain mandatory.
production_args=(
-f deploy/charts/buzz-push-gateway/values-production.yaml
--set 'image.digest=sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
--set 'gatewayOrigin=https://push.example'
--set 'profiles.dogfood.appAttestAppId=REALTEAM.xyz.block.buzz.dogfood.mobile'
--set 'networkPolicy.postgresEgressCidrs[0]=10.42.0.0/16'
)
@@ -20,6 +22,7 @@ helm template push deploy/charts/buzz-push-gateway "${production_args[@]}" >"$pr
# Gateway API remains an explicit supported ingress mode when an operator opts
# in and supplies the environment-owned parent.
helm template push deploy/charts/buzz-push-gateway \
"${gateway_origin_arg[@]}" \
--set httpRoute.enabled=true \
--set 'httpRoute.parentRefs[0].name=production-gateway' \
--set 'httpRoute.parentRefs[0].namespace=gateway-system' \
@@ -63,8 +66,12 @@ required = Set.new(%w[
DATABASE_URL BUZZ_PUSH_DOGFOOD_APNS_CERT_PATH
BUZZ_PUSH_DOGFOOD_APNS_TOPIC BUZZ_PUSH_DOGFOOD_APP_ATTEST_APP_ID
BUZZ_PUSH_GRANT_KEYS BUZZ_PUSH_TOKEN_KEYS BUZZ_PUSH_MAX_GRANT_LIFETIME_SECONDS
BUZZ_PUSH_GATEWAY_ORIGIN
])
assert!(required.subset?(env_names))
gateway_origin = d.dig("spec", "template", "spec", "containers", 0, "env")
.find { |entry| entry["name"] == "BUZZ_PUSH_GATEWAY_ORIGIN" }
assert!(gateway_origin["value"] == "https://push.example")
assert!(!env_names.any? { |name| name.include?("APP_STORE") })
apns_volume = d.dig("spec", "template", "spec", "volumes").find { |volume| volume["name"] == "apns-dogfood" }
assert!(apns_volume.dig("secret", "defaultMode") == 0o400, apns_volume.inspect)
@@ -100,12 +107,26 @@ production_image = production_deployment.dig("spec", "template", "spec", "contai
assert!(production_image == "ghcr.io/block/buzz-push-gateway@sha256:#{"a" * 64}", production_image.inspect)
route = YAML.load_stream(File.read(ARGV[2])).compact.find { |x| x["kind"] == "HTTPRoute" }
assert!(!route.dig("spec", "parentRefs").empty?)
assert!(route.dig("spec", "hostnames").include?("push.buzz.xyz"))
assert!(route.dig("spec", "hostnames") == ["push.example"])
RUBY
# A gateway origin is a required deployment input, even when HTTPRoute is off.
if helm template push deploy/charts/buzz-push-gateway >/dev/null 2>&1; then
echo 'expected missing gatewayOrigin to fail' >&2
exit 1
fi
for invalid_origin in 'http://push.example' 'https://push.example:8443' 'https://push.example/base'; do
if helm template push deploy/charts/buzz-push-gateway \
--set "gatewayOrigin=$invalid_origin" >/dev/null 2>&1; then
echo "expected malformed gatewayOrigin $invalid_origin to fail" >&2
exit 1
fi
done
# Legacy token-auth values must fail rather than silently selecting the default
# certificate Secret.
if helm template push deploy/charts/buzz-push-gateway \
"${gateway_origin_arg[@]}" \
--set apnsKey.secretName=legacy-apns-secret \
--set apnsKey.secretKey=legacy-provider.p8 >/dev/null 2>&1; then
echo 'expected legacy apnsKey values to fail schema validation' >&2
@@ -113,7 +134,7 @@ if helm template push deploy/charts/buzz-push-gateway \
fi
# Enabling a route without a Gateway attachment must fail schema validation.
if helm template push deploy/charts/buzz-push-gateway --set httpRoute.enabled=true >/dev/null 2>&1; then
if helm template push deploy/charts/buzz-push-gateway "${gateway_origin_arg[@]}" --set httpRoute.enabled=true >/dev/null 2>&1; then
echo 'expected httpRoute.enabled=true without parentRefs to fail' >&2
exit 1
fi
@@ -129,6 +150,7 @@ fi
# keyed to the named monitoring source — never a blanket 8081 rule.
monitoring_out=$(mktemp)
helm template push deploy/charts/buzz-push-gateway \
"${gateway_origin_arg[@]}" \
--set podMonitor.enabled=true \
--set prometheusRule.enabled=true \
--set networkPolicy.monitoring.enabled=true \
@@ -162,9 +184,11 @@ RUBY
# Datadog discovers the same private endpoint from pod annotations and needs no
# prometheus-operator CRDs. Its agent ingress remains selector-scoped.
helm lint deploy/charts/buzz-push-gateway \
-f deploy/charts/buzz-push-gateway/tests/datadog-values.yaml >/dev/null
-f deploy/charts/buzz-push-gateway/tests/datadog-values.yaml \
"${gateway_origin_arg[@]}" >/dev/null
helm template push deploy/charts/buzz-push-gateway \
-f deploy/charts/buzz-push-gateway/tests/datadog-values.yaml \
"${gateway_origin_arg[@]}" \
>"$datadog_out"
env -u GEM_HOME -u GEM_PATH -u RUBYLIB -u RUBYOPT ruby -rjson -ryaml -rset \
@@ -200,6 +224,7 @@ RUBY
# Negative: monitoring enabled with default empty selectors must fail (would
# otherwise render a blanket 8081 rule matching all namespaces/pods).
if helm template push deploy/charts/buzz-push-gateway \
"${gateway_origin_arg[@]}" \
--set podMonitor.enabled=true \
--set networkPolicy.monitoring.enabled=true >/dev/null 2>&1; then
echo 'expected monitoring.enabled with empty selectors to fail' >&2
@@ -209,6 +234,7 @@ fi
# Negative: PodMonitor without ingress is an unreachable scraper and must fail.
# Scoped ingress without PodMonitor is valid for annotation-discovered agents.
if helm template push deploy/charts/buzz-push-gateway \
"${gateway_origin_arg[@]}" \
--set podMonitor.enabled=true \
--set 'networkPolicy.monitoring.namespaceSelector.kubernetes\.io/metadata\.name=monitoring' \
--set 'networkPolicy.monitoring.podSelector.app\.kubernetes\.io/name=prometheus' \
@@ -219,6 +245,7 @@ fi
# Negative: retry-ratio threshold is a fraction; a value > 1 must fail schema.
if helm template push deploy/charts/buzz-push-gateway \
"${gateway_origin_arg[@]}" \
--set prometheusRule.enabled=true \
--set prometheusRule.apnsRetryRatioThreshold=2 >/dev/null 2>&1; then
echo 'expected apnsRetryRatioThreshold=2 to fail' >&2

View File

@@ -3,16 +3,15 @@
image:
tag: ""
digest: ""
gatewayOrigin: ""
profiles:
dogfood:
appAttestAppId: ""
httpRoute:
# Keep disabled when the platform already routes push.buzz.xyz to this
# Service. Gateway API users enable it and inject an explicit parentRef.
# Keep disabled when the platform already routes the configured gateway
# origin to this Service. Gateway API users enable it and inject a parentRef.
enabled: false
parentRefs: []
hostnames:
- push.buzz.xyz
networkPolicy:
apnsEgressCidrs:
- 0.0.0.0/0

View File

@@ -11,8 +11,10 @@
"type": "string",
"minLength": 1
},
"publicDeliveryUrl": {
"const": "https://push.buzz.xyz/v1/deliveries/apns"
"gatewayOrigin": {
"type": "string",
"format": "uri",
"pattern": "^https://[^/?#@:]+/?$"
},
"maxGrantLifetimeSeconds": {
"type": "integer",
@@ -40,10 +42,10 @@
},
"httpRoute": {
"type": "object",
"additionalProperties": false,
"required": [
"enabled",
"parentRefs",
"hostnames"
"parentRefs"
],
"properties": {
"enabled": {
@@ -51,12 +53,6 @@
},
"parentRefs": {
"type": "array"
},
"hostnames": {
"type": "array",
"contains": {
"const": "push.buzz.xyz"
}
}
},
"allOf": [
@@ -310,7 +306,7 @@
"required": [
"replicaCount",
"existingSecret",
"publicDeliveryUrl",
"gatewayOrigin",
"maxGrantLifetimeSeconds",
"profiles",
"httpRoute",

View File

@@ -18,7 +18,9 @@ migration:
resources:
requests: {cpu: 50m, memory: 64Mi}
limits: {cpu: 250m, memory: 128Mi}
publicDeliveryUrl: https://push.buzz.xyz/v1/deliveries/apns
# Exact externally reachable HTTPS origin. The gateway derives transport routes
# from this value; NIP-PL v1 App Attest audiences remain registered constants.
gatewayOrigin: ""
maxGrantLifetimeSeconds: 2592000
profiles:
dogfood:
@@ -41,7 +43,6 @@ httpRoute:
# existing ingress or service mesh route should keep this disabled.
enabled: false
parentRefs: []
hostnames: [push.buzz.xyz]
resources:
requests: {cpu: 100m, memory: 128Mi}
limits: {cpu: "1", memory: 512Mi}