Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions crates/buzz-db/src/runtime/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,8 @@ mod postgres_tests {

const TEST_DB_URL: &str = "postgres://buzz:buzz_dev@localhost:5432/buzz"; // sadscan:disable np.postgres.1

/// Connection parameters parsed out of a `postgres://user:pass@host:port/db`
/// URL so the parity test can pass them to the `bin/pgschema` binary, which
/// Connection parameters parsed out of a PostgreSQL URL so the parity test
/// can pass them to the `bin/pgschema` binary, which
/// takes discrete `--host/--port/--user/--password/--db` flags rather than a
/// URL. Only the shapes this test emits (`BUZZ_TEST_DATABASE_URL` /
/// `DATABASE_URL` / `TEST_DB_URL`) are supported.
Expand Down Expand Up @@ -699,7 +699,7 @@ mod postgres_tests {
let mut migrations: Vec<_> = MIGRATOR.iter().collect();
migrations.sort_by_key(|migration| migration.version);

assert_eq!(migrations.len(), 42);
assert_eq!(migrations.len(), 43);
assert_eq!(migrations[0].version, 1);
assert_eq!(&*migrations[0].description, "initial schema");
assert!(migrations[0]
Expand Down Expand Up @@ -1280,6 +1280,18 @@ mod postgres_tests {
extract_excluded_table_array(desired_schema),
"schema.sql exclusion list drifted from migration 0042"
);

// Brownfield relay databases created through SQLx still carry the
// production/sandbox constraint from 0015. Converge them to the same
// dogfood-only authority declared by the desired-state schema.
assert_eq!(migrations[42].version, 43);
let dogfood_profile = migrations[42].sql.as_str();
assert!(dogfood_profile.contains("DELETE FROM push_gateway_delegations"));
assert!(dogfood_profile.contains("DELETE FROM push_gateway_installations"));
assert!(dogfood_profile
.contains("DROP CONSTRAINT push_gateway_installations_app_profile_check"));
assert!(dogfood_profile.contains("CHECK (app_profile = 'buzz-ios-dogfood')"));
assert!(desired_schema.contains("CHECK (app_profile = 'buzz-ios-dogfood')"));
}

#[test]
Expand Down
94 changes: 93 additions & 1 deletion crates/buzz-db/src/runtime/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use buzz_core::CommunityId;
use sqlx::{Connection, PgPool};
use uuid::Uuid;

const TEST_DB_URL: &str = "postgres://buzz:buzz_dev@localhost:5432/buzz";
const TEST_DB_URL: &str = "postgres://buzz:buzz_dev@localhost:5432/buzz"; // sadscan:disable np.postgres.1 -- local test-only credentials

async fn setup_db() -> Db {
let database_url = std::env::var("TEST_DATABASE_URL").unwrap_or_else(|_| TEST_DB_URL.into());
Expand Down Expand Up @@ -399,6 +399,98 @@ async fn drop_scratch_db(admin: &PgPool, pool: PgPool, name: &str) {
.await;
}

#[tokio::test]
#[ignore = "requires Postgres"]
async fn push_gateway_profile_migration_converges_brownfield_authority() {
let admin = PgPool::connect(&admin_url().await)
.await
.expect("connect admin database");
let (pool, name) = create_scratch_db_through(&admin, "push_profile", Some(42)).await;
let installation_id = Uuid::new_v4();
let now = chrono::Utc::now();

sqlx::query(
"INSERT INTO push_gateway_installations(\
id, app_attest_key_id, app_attest_public_key, assertion_counter, app_profile, \
token_ciphertext, token_fingerprint, endpoint_epoch, expires_at) \
VALUES($1, $2, $3, 0, 'buzz-ios-production', $4, $5, 1, $6)",
)
.bind(installation_id)
.bind(vec![1_u8])
.bind(vec![2_u8; 33])
.bind(vec![3_u8])
.bind(vec![4_u8; 32])
.bind(now + chrono::Duration::days(1))
.execute(&pool)
.await
.expect("insert legacy production installation");
sqlx::query(
"INSERT INTO push_gateway_delegations(\
id, installation_id, relay_pubkey, endpoint_epoch, generation, not_before, expires_at) \
VALUES($1, $2, $3, 1, 1, $4, $5)",
)
.bind(Uuid::new_v4())
.bind(installation_id)
.bind(vec![5_u8; 32])
.bind(now)
.bind(now + chrono::Duration::hours(1))
.execute(&pool)
.await
.expect("insert delegation for legacy installation");

migration::run_migrations(&pool)
.await
.expect("apply dogfood-only migration");

let legacy_installations: i64 =
sqlx::query_scalar("SELECT count(*) FROM push_gateway_installations")
.fetch_one(&pool)
.await
.expect("count legacy installations");
let legacy_delegations: i64 =
sqlx::query_scalar("SELECT count(*) FROM push_gateway_delegations")
.fetch_one(&pool)
.await
.expect("count legacy delegations");
assert_eq!(legacy_installations, 0);
assert_eq!(legacy_delegations, 0);

sqlx::query(
"INSERT INTO push_gateway_installations(\
id, app_attest_key_id, app_attest_public_key, assertion_counter, app_profile, \
token_ciphertext, token_fingerprint, endpoint_epoch, expires_at) \
VALUES($1, $2, $3, 0, 'buzz-ios-dogfood', $4, $5, 1, $6)",
)
.bind(Uuid::new_v4())
.bind(vec![6_u8])
.bind(vec![7_u8; 33])
.bind(vec![8_u8])
.bind(vec![9_u8; 32])
.bind(now + chrono::Duration::days(1))
.execute(&pool)
.await
.expect("dogfood installation is accepted after migration");

let sandbox = sqlx::query(
"INSERT INTO push_gateway_installations(\
id, app_attest_key_id, app_attest_public_key, assertion_counter, app_profile, \
token_ciphertext, token_fingerprint, endpoint_epoch, expires_at) \
VALUES($1, $2, $3, 0, 'buzz-ios-sandbox', $4, $5, 1, $6)",
)
.bind(Uuid::new_v4())
.bind(vec![10_u8])
.bind(vec![11_u8; 33])
.bind(vec![12_u8])
.bind(vec![13_u8; 32])
.bind(now + chrono::Duration::days(1))
.execute(&pool)
.await;
assert!(sandbox.is_err(), "legacy sandbox profile must be rejected");

drop_scratch_db(&admin, pool, &name).await;
admin.close().await;
}

/// Insert identical community + channel rows into a database so the same
/// (community, channel) ids resolve in both writer and replica.
async fn seed_community_channel(
Expand Down
2 changes: 1 addition & 1 deletion deploy/charts/buzz-push-gateway/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ apiVersion: v2
# branches (see docs/push-gateway-deployment.md, "Gateway chart release").
name: buzz-push-gateway
description: Public capability-gated APNs last-hop gateway for Buzz
version: 0.1.0
version: 0.2.0
appVersion: "0.1.0"
type: application
3 changes: 3 additions & 0 deletions deploy/charts/buzz-push-gateway/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ spec:
template:
metadata:
labels: {{- include "push.runtimeLabels" . | nindent 8 }}
{{- with .Values.podAnnotations }}
annotations: {{- toYaml . | nindent 8 }}
{{- end }}
spec:
automountServiceAccountToken: false
terminationGracePeriodSeconds: 60
Expand Down
30 changes: 30 additions & 0 deletions deploy/charts/buzz-push-gateway/tests/datadog-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Render-only fixture proving Datadog Autodiscovery can scrape the private
# metrics listener without installing prometheus-operator CRDs. Deployment
# repositories must replace these illustrative selectors with their agent's
# actual namespace and pod labels.
podAnnotations:
ad.datadoghq.com/gateway.checks: |
{
"openmetrics": {
"init_config": {},
"instances": [
{
"openmetrics_endpoint": "http://%%host%%:8081/metrics",
"service": "buzz-push-gateway",
"namespace": "block.buzz_push_gateway",
"metrics": ["push_gateway_.*"],
"histogram_buckets_as_distributions": true,
"send_distribution_buckets": true,
"send_monotonic_counter": true,
"collect_counters_with_distributions": true
}
]
}
}
networkPolicy:
monitoring:
enabled: true
namespaceSelector:
kubernetes.io/metadata.name: datadog
podSelector:
app.kubernetes.io/name: datadog-agent
21 changes: 21 additions & 0 deletions deploy/charts/buzz-push-gateway/tests/release-contract.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@ set -euo pipefail
env -u GEM_HOME -u GEM_PATH -u RUBYLIB -u RUBYOPT ruby -ryaml <<'RUBY'
auto_text = File.read('.github/workflows/auto-tag-on-release-pr-merge.yml')
publish_text = File.read('.github/workflows/push-gateway-helm-chart.yml')
deployment_text = File.read('docs/push-gateway-deployment.md')
chart = YAML.load_file('deploy/charts/buzz-push-gateway/Chart.yaml')
# Parse first, then pin the tag producer and consumer strings whose agreement
# makes this a reachable lane rather than an orphan publisher.
YAML.load(auto_text)
YAML.load(publish_text)
version = chart.fetch('version').to_s
raise "gateway chart version is not semver: #{version}" unless version.match?(/\A\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\z/)
workspace_package = File.read('Cargo.toml').match(/\[workspace\.package\](.*?)(?=\n\[|\z)/m)
raise "workspace package metadata is missing" unless workspace_package
binary_version = workspace_package[1].match(/^version\s*=\s*"([^"]+)"/)&.[](1)
raise "workspace package version is missing" unless binary_version
unless chart.fetch('appVersion').to_s == binary_version
raise "gateway chart appVersion does not match packaged binary #{binary_version}"
end
[
'push-chart-release/*)',
'VERSION="${BRANCH#push-chart-release/}"',
Expand All @@ -26,4 +37,14 @@ end
].each do |needle|
raise "missing gateway chart publisher contract: #{needle}" unless publish_text.include?(needle)
end
[
'inspect and fetch the published chart version',
'helm show chart oci://ghcr.io/block/buzz/charts/buzz-push-gateway --version X.Y.Z',
'helm pull oci://ghcr.io/block/buzz/charts/buzz-push-gateway --version X.Y.Z',
].each do |needle|
raise "missing gateway chart retrieval guidance: #{needle}" unless deployment_text.include?(needle)
end
if deployment_text.include?('verify the immutable chart artifact')
raise 'gateway chart retrieval guidance overstates authenticity verification'
end
RUBY
81 changes: 61 additions & 20 deletions deploy/charts/buzz-push-gateway/tests/render.sh
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
out=$(mktemp); production_out=$(mktemp)
trap 'rm -f "$out" "$production_out"' EXIT
out=$(mktemp); production_out=$(mktemp); route_out=$(mktemp); datadog_out=$(mktemp)
trap 'rm -f "$out" "$production_out" "$route_out" "$datadog_out" "${monitoring_out:-}"' EXIT

# 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"
# Production values must attach push.buzz.xyz to an explicit Gateway.
# 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 'profiles.dogfood.appAttestAppId=REALTEAM.xyz.block.buzz.dogfood.mobile'
--set 'httpRoute.parentRefs[0].name=production-gateway'
--set 'httpRoute.parentRefs[0].namespace=gateway-system'
--set 'networkPolicy.postgresEgressCidrs[0]=10.42.0.0/16'
)
helm lint deploy/charts/buzz-push-gateway "${production_args[@]}" >/dev/null
helm template push deploy/charts/buzz-push-gateway "${production_args[@]}" >"$production_out"

# 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 \
--set httpRoute.enabled=true \
--set 'httpRoute.parentRefs[0].name=production-gateway' \
--set 'httpRoute.parentRefs[0].namespace=gateway-system' \
>"$route_out"

env -u GEM_HOME -u GEM_PATH -u RUBYLIB -u RUBYOPT ruby -ryaml -rset \
- "$out" "$production_out" <<'RUBY'
- "$out" "$production_out" "$route_out" <<'RUBY'
def assert!(condition, detail = "assertion failed")
raise detail unless condition
end
Expand All @@ -38,6 +45,7 @@ migration = runtime.merge("app.kubernetes.io/component" => "migration")
assert!(svc.dig("spec", "selector") == runtime)
assert!(d.dig("spec", "selector", "matchLabels") == runtime)
assert!(d.dig("spec", "template", "metadata", "labels") == runtime)
assert!(d.dig("spec", "template", "metadata", "annotations").nil?)
assert!(j.dig("spec", "template", "metadata", "labels") == migration)
assert!(svc.dig("spec", "selector") != j.dig("spec", "template", "metadata", "labels"))
jenv = j.dig("spec", "template", "spec", "containers", 0, "env").to_h { |entry| [entry["name"], entry] }
Expand Down Expand Up @@ -86,7 +94,11 @@ ingress_ports = np.dig("spec", "ingress")
.flat_map { |rule| rule.fetch("ports", []) }.map { |port| port["port"] }.to_set
assert!(ingress_ports == Set[8080], ingress_ports.inspect)
production = YAML.load_stream(File.read(ARGV[1])).compact
route = production.find { |x| x["kind"] == "HTTPRoute" }
assert!(!production.any? { |x| x["kind"] == "HTTPRoute" })
production_deployment = production.find { |x| x["kind"] == "Deployment" }
production_image = production_deployment.dig("spec", "template", "spec", "containers", 0, "image")
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"))
RUBY
Expand All @@ -107,15 +119,15 @@ if helm template push deploy/charts/buzz-push-gateway --set httpRoute.enabled=tr
fi

# The checked-in production contract is intentionally undeployable until CI or
# the release system supplies an immutable digest and environment-owned values.
# the release system supplies its environment-owned values.
if helm template push deploy/charts/buzz-push-gateway -f deploy/charts/buzz-push-gateway/values-production.yaml >/dev/null 2>&1; then
echo 'expected uninjected production values to fail' >&2
exit 1
fi

# Enabling observability renders the scrape CRDs and adds a scoped 8081 ingress
# keyed to the named monitoring source — never a blanket 8081 rule.
monitoring_out=$(mktemp); trap 'rm -f "$out" "$production_out" "$monitoring_out"' EXIT
monitoring_out=$(mktemp)
helm template push deploy/charts/buzz-push-gateway \
--set podMonitor.enabled=true \
--set prometheusRule.enabled=true \
Expand Down Expand Up @@ -147,6 +159,44 @@ from = monitoring[0].fetch("from")[0]
assert!(!from.dig("namespaceSelector", "matchLabels").empty? && !from.dig("podSelector", "matchLabels").empty?, from.inspect)
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
helm template push deploy/charts/buzz-push-gateway \
-f deploy/charts/buzz-push-gateway/tests/datadog-values.yaml \
>"$datadog_out"

env -u GEM_HOME -u GEM_PATH -u RUBYLIB -u RUBYOPT ruby -rjson -ryaml -rset \
- "$datadog_out" <<'RUBY'
def assert!(condition, detail = "assertion failed")
raise detail unless condition
end

xs = YAML.load_stream(File.read(ARGV[0])).compact
assert!(!xs.any? { |x| %w[PodMonitor PrometheusRule].include?(x["kind"]) })
deployment = xs.find { |x| x["kind"] == "Deployment" }
raw_check = deployment.dig(
"spec", "template", "metadata", "annotations",
"ad.datadoghq.com/gateway.checks",
)
check = JSON.parse(raw_check)
instance = check.dig("openmetrics", "instances", 0)
assert!(instance["openmetrics_endpoint"] == "http://%%host%%:8081/metrics", instance.inspect)
assert!(instance["metrics"] == ["push_gateway_.*"], instance.inspect)

np = xs.find do |x|
x["kind"] == "NetworkPolicy" && x.dig("metadata", "name") == "push-buzz-push-gateway"
end
monitoring = np.dig("spec", "ingress").select do |rule|
rule.fetch("ports", []).map { |port| port["port"] }.to_set == Set[8081]
end
assert!(monitoring.length == 1, "exactly one scoped Datadog 8081 ingress rule")
from = monitoring[0].fetch("from")[0]
assert!(!from.dig("namespaceSelector", "matchLabels").empty?, from.inspect)
assert!(!from.dig("podSelector", "matchLabels").empty?, from.inspect)
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 \
Expand All @@ -156,9 +206,8 @@ if helm template push deploy/charts/buzz-push-gateway \
exit 1
fi

# Negative: scrape flags must be coupled. PodMonitor without ingress = an
# unreachable scraper; ingress without a PodMonitor = an open hole with no
# scraper. Both mismatches must fail schema validation.
# 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 \
--set podMonitor.enabled=true \
--set 'networkPolicy.monitoring.namespaceSelector.kubernetes\.io/metadata\.name=monitoring' \
Expand All @@ -167,14 +216,6 @@ if helm template push deploy/charts/buzz-push-gateway \
echo 'expected podMonitor.enabled without monitoring ingress to fail' >&2
exit 1
fi
if helm template push deploy/charts/buzz-push-gateway \
--set networkPolicy.monitoring.enabled=true \
--set 'networkPolicy.monitoring.namespaceSelector.kubernetes\.io/metadata\.name=monitoring' \
--set 'networkPolicy.monitoring.podSelector.app\.kubernetes\.io/name=prometheus' \
>/dev/null 2>&1; then
echo 'expected monitoring ingress without podMonitor.enabled to fail' >&2
exit 1
fi

# Negative: retry-ratio threshold is a fraction; a value > 1 must fail schema.
if helm template push deploy/charts/buzz-push-gateway \
Expand Down
4 changes: 3 additions & 1 deletion deploy/charts/buzz-push-gateway/values-production.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ profiles:
dogfood:
appAttestAppId: ""
httpRoute:
enabled: true
# Keep disabled when the platform already routes push.buzz.xyz to this
# Service. Gateway API users enable it and inject an explicit parentRef.
enabled: false
parentRefs: []
hostnames:
- push.buzz.xyz
Expand Down
Loading
Loading